Detect repeated intervals: initial implementation
This commit is contained in:
parent
ba88317fb9
commit
5c462d545f
|
|
@ -0,0 +1,85 @@
|
|||
import { Interval } from "./ast";
|
||||
import { detectRepeats } from "./detectRepeats";
|
||||
import { Seconds } from "./types";
|
||||
|
||||
describe("detectRepeats()", () => {
|
||||
it("does nothing with empty array", () => {
|
||||
expect(detectRepeats([])).toEqual([]);
|
||||
});
|
||||
|
||||
it("does nothing when no interval repeats", () => {
|
||||
const intervals: Interval[] = [
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(30), intensity: { from: 1.2, to: 1.2 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(60), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
];
|
||||
expect(detectRepeats(intervals)).toEqual(intervals);
|
||||
});
|
||||
|
||||
it("detects whole workout consisting of repetitions", () => {
|
||||
const intervals: Interval[] = [
|
||||
{ type: "Interval", duration: new Seconds(120), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(120), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(120), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(120), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
];
|
||||
expect(detectRepeats(intervals)).toEqual([
|
||||
{
|
||||
times: 4,
|
||||
intervals: [
|
||||
{ type: "Interval", duration: new Seconds(120), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("detects repetitions in the middle of workout", () => {
|
||||
const intervals: Interval[] = [
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.5, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(120), intensity: { from: 0.2, to: 0.2 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(120), intensity: { from: 0.2, to: 0.2 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(60), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
];
|
||||
expect(detectRepeats(intervals)).toEqual([
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.5, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(120), intensity: { from: 0.2, to: 0.2 }, comments: [] },
|
||||
{
|
||||
times: 4,
|
||||
intervals: [
|
||||
{ type: "Interval", duration: new Seconds(60), intensity: { from: 1, to: 1 }, comments: [] },
|
||||
{ type: "Rest", duration: new Seconds(60), intensity: { from: 0.5, to: 0.5 }, comments: [] },
|
||||
],
|
||||
},
|
||||
{ type: "Rest", duration: new Seconds(120), intensity: { from: 0.2, to: 0.2 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(60), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not consider warmup/cooldown-range intervals to be repeatable", () => {
|
||||
const intervals: Interval[] = [
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.1, to: 1 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(120), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.1, to: 1 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(120), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.1, to: 1 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(120), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
{ type: "Warmup", duration: new Seconds(60), intensity: { from: 0.1, to: 1 }, comments: [] },
|
||||
{ type: "Cooldown", duration: new Seconds(120), intensity: { from: 1, to: 0.5 }, comments: [] },
|
||||
];
|
||||
expect(detectRepeats(intervals)).toEqual(intervals);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { equals } from "ramda";
|
||||
import { Interval } from "./ast";
|
||||
|
||||
export type RepeatedInterval = {
|
||||
times: number;
|
||||
intervals: Interval[];
|
||||
};
|
||||
|
||||
const windowSize = 2;
|
||||
|
||||
const countRepetitions = (reference: Interval[], intervals: Interval[], startIndex: number): number => {
|
||||
let repeats = 1;
|
||||
while (startIndex + repeats * windowSize < intervals.length) {
|
||||
const from = startIndex + repeats * windowSize;
|
||||
const possibleRepeat = intervals.slice(from, from + windowSize);
|
||||
if (equals(reference, possibleRepeat)) {
|
||||
repeats++;
|
||||
} else {
|
||||
return repeats;
|
||||
}
|
||||
}
|
||||
return repeats;
|
||||
};
|
||||
|
||||
const isRangeInterval = (interval: Interval): boolean => interval.intensity.from !== interval.intensity.to;
|
||||
|
||||
export const detectRepeats = (intervals: Interval[]): (Interval | RepeatedInterval)[] => {
|
||||
if (intervals.length < windowSize) {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
const processed: (Interval | RepeatedInterval)[] = [];
|
||||
let i = 0;
|
||||
while (i < intervals.length) {
|
||||
// Ignore warmup/cooldown-range intervals
|
||||
if (isRangeInterval(intervals[i])) {
|
||||
processed.push(intervals[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const reference = intervals.slice(i, i + windowSize);
|
||||
const repeats = countRepetitions(reference, intervals, i);
|
||||
if (repeats > 1) {
|
||||
processed.push({
|
||||
times: repeats,
|
||||
intervals: reference,
|
||||
});
|
||||
i += repeats * windowSize;
|
||||
} else {
|
||||
processed.push(intervals[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
};
|
||||
Loading…
Reference in New Issue