From 72a02f2d1981913b9dcccd8a81e2cf7195947bec Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Fri, 2 Oct 2020 13:32:36 +0300 Subject: [PATCH] Implement chunkRangeIntervals() utility --- src/index.ts | 3 +- src/utils/chunkRangeIntervals.test.ts | 154 ++++++++++++++++++++++++++ src/utils/chunkRangeIntervals.ts | 49 ++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/utils/chunkRangeIntervals.test.ts create mode 100644 src/utils/chunkRangeIntervals.ts diff --git a/src/index.ts b/src/index.ts index 15349b7..0f5718c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,8 @@ export { Workout, Interval, Comment } from "./ast"; export { Duration } from "./Duration"; export { Intensity, ConstantIntensity, RangeIntensity, FreeIntensity } from "./Intensity"; -// stats utils +// utils export { totalDuration } from "./stats/totalDuration"; export { intensityToZoneIndex, ZoneIndex } from "./stats/zoneDistribution"; export { maximumIntensity } from "./stats/maximumIntensity"; +export { chunkRangeIntervals } from "./utils/chunkRangeIntervals"; diff --git a/src/utils/chunkRangeIntervals.test.ts b/src/utils/chunkRangeIntervals.test.ts new file mode 100644 index 0000000..9a059f9 --- /dev/null +++ b/src/utils/chunkRangeIntervals.test.ts @@ -0,0 +1,154 @@ +import { Interval } from "../ast"; +import { Duration } from "../Duration"; +import { ConstantIntensity, RangeIntensity } from "../Intensity"; +import { chunkRangeIntervals } from "./chunkRangeIntervals"; + +describe("chunkRangeIntervals()", () => { + const minute = new Duration(60); + + it("does nothing with empty array", () => { + expect(chunkRangeIntervals([], minute)).toEqual([]); + }); + + it("does nothing with constant-intensity intervals", () => { + const intervals: Interval[] = [ + { + type: "Interval", + duration: new Duration(2 * 60), + intensity: new ConstantIntensity(0.7), + comments: [], + }, + { + type: "Interval", + duration: new Duration(10 * 60), + intensity: new ConstantIntensity(1), + comments: [], + }, + { + type: "Rest", + duration: new Duration(30), + intensity: new ConstantIntensity(0.5), + comments: [], + }, + ]; + + expect(chunkRangeIntervals(intervals, minute)).toEqual(intervals); + }); + + it("converts 1-minute range-interval to 1-minute constant-interval", () => { + expect( + chunkRangeIntervals( + [ + { + type: "Warmup", + duration: minute, + intensity: new RangeIntensity(0.5, 1), + comments: [], + }, + ], + minute, + ), + ).toMatchInlineSnapshot(` + Array [ + Object { + "comments": Array [], + "duration": Duration { + "seconds": 60, + }, + "intensity": ConstantIntensity { + "_value": 0.75, + }, + "type": "Warmup", + }, + ] + `); + }); + + it("splits 3-minute range-interval to three 1-minute constant-intervals", () => { + expect( + chunkRangeIntervals( + [ + { + type: "Warmup", + duration: new Duration(3 * 60), + intensity: new RangeIntensity(0.5, 1), + comments: [], + }, + ], + minute, + ), + ).toMatchInlineSnapshot(` + Array [ + Object { + "comments": Array [], + "duration": Duration { + "seconds": 60, + }, + "intensity": ConstantIntensity { + "_value": 0.5833333333333334, + }, + "type": "Warmup", + }, + Object { + "comments": Array [], + "duration": Duration { + "seconds": 60, + }, + "intensity": ConstantIntensity { + "_value": 0.75, + }, + "type": "Warmup", + }, + Object { + "comments": Array [], + "duration": Duration { + "seconds": 60, + }, + "intensity": ConstantIntensity { + "_value": 0.9166666666666667, + }, + "type": "Warmup", + }, + ] + `); + }); + + it("splits 1:30 range-interval to 1min & 30sec constant-intervals", () => { + expect( + chunkRangeIntervals( + [ + { + type: "Warmup", + duration: new Duration(60 + 30), + intensity: new RangeIntensity(0.5, 1), + comments: [], + }, + ], + minute, + ), + ).toMatchInlineSnapshot(` + Array [ + Object { + "comments": Array [], + "duration": Duration { + "seconds": 60, + }, + "intensity": ConstantIntensity { + "_value": 0.6666666666666666, + }, + "type": "Warmup", + }, + Object { + "comments": Array [], + "duration": Duration { + "seconds": 30, + }, + "intensity": ConstantIntensity { + "_value": 0.9166666666666667, + }, + "type": "Warmup", + }, + ] + `); + }); +}); diff --git a/src/utils/chunkRangeIntervals.ts b/src/utils/chunkRangeIntervals.ts new file mode 100644 index 0000000..4f2f7a3 --- /dev/null +++ b/src/utils/chunkRangeIntervals.ts @@ -0,0 +1,49 @@ +import { chain, curry } from "ramda"; +import { Interval } from "../ast"; +import { Duration } from "../Duration"; +import { ConstantIntensity, Intensity, RangeIntensity } from "../Intensity"; + +const chunkDuration = (seconds: number, chunkSize: Duration, intervalDuration: Duration): Duration => { + return seconds + chunkSize.seconds > intervalDuration.seconds + ? new Duration(intervalDuration.seconds % chunkSize.seconds) + : chunkSize; +}; + +const chunkIntensity = ( + startSeconds: number, + chunkSize: Duration, + { start, end }: Intensity, + intervalDuration: Duration, +): ConstantIntensity => { + const endSeconds = + startSeconds + chunkSize.seconds > intervalDuration.seconds + ? intervalDuration.seconds + : startSeconds + chunkSize.seconds; + + const middleSeconds = (startSeconds + endSeconds) / 2; + + return new ConstantIntensity(start + (end - start) * (middleSeconds / intervalDuration.seconds)); +}; + +const chunkInterval = curry((chunkSize: Duration, interval: Interval): Interval[] => { + if (!(interval.intensity instanceof RangeIntensity)) { + return [interval]; + } + + const intervals: Interval[] = []; + for (let seconds = 0; seconds < interval.duration.seconds; seconds += chunkSize.seconds) { + intervals.push({ + ...interval, + duration: chunkDuration(seconds, chunkSize, interval.duration), + intensity: chunkIntensity(seconds, chunkSize, interval.intensity, interval.duration), + comments: [], // TODO: for now, ignoring comments + }); + } + return intervals; +}); + +/** + * Breaks intervals that use RangeIntensity into multiple intervals with ConstantIntensity + */ +export const chunkRangeIntervals = (intervals: Interval[], chunkSize: Duration): Interval[] => + chain(chunkInterval(chunkSize), intervals);