mirror of
https://github.com/denoland/std.git
synced 2024-11-21 12:40:03 +00:00
feat(collections/unstable): support Iterable
argument in slidingWindows
(#6095)
This commit is contained in:
parent
825f31160f
commit
45be5d319d
@ -51,6 +51,7 @@
|
|||||||
"./unstable-drop-last-while": "./unstable_drop_last_while.ts",
|
"./unstable-drop-last-while": "./unstable_drop_last_while.ts",
|
||||||
"./unstable-intersect": "./unstable_intersect.ts",
|
"./unstable-intersect": "./unstable_intersect.ts",
|
||||||
"./unstable-sample": "./unstable_sample.ts",
|
"./unstable-sample": "./unstable_sample.ts",
|
||||||
|
"./unstable-sliding-windows": "./unstable_sliding_windows.ts",
|
||||||
"./unstable-sort-by": "./unstable_sort_by.ts",
|
"./unstable-sort-by": "./unstable_sort_by.ts",
|
||||||
"./unstable-take-last-while": "./unstable_take_last_while.ts",
|
"./unstable-take-last-while": "./unstable_take_last_while.ts",
|
||||||
"./unstable-take-while": "./unstable_take_while.ts",
|
"./unstable-take-while": "./unstable_take_while.ts",
|
||||||
|
101
collections/unstable_sliding_windows.ts
Normal file
101
collections/unstable_sliding_windows.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
// This module is browser compatible.
|
||||||
|
|
||||||
|
/** Options for {@linkcode slidingWindows}. */
|
||||||
|
export interface SlidingWindowsOptions {
|
||||||
|
/**
|
||||||
|
* If step is set, each window will start that many elements after the last
|
||||||
|
* window's start.
|
||||||
|
*
|
||||||
|
* @default {1}
|
||||||
|
*/
|
||||||
|
step?: number;
|
||||||
|
/**
|
||||||
|
* If partial is set, windows will be generated for the last elements of the
|
||||||
|
* collection, resulting in some undefined values if size is greater than 1.
|
||||||
|
*
|
||||||
|
* @default {false}
|
||||||
|
*/
|
||||||
|
partial?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates sliding views of the given iterable of the given size and returns an
|
||||||
|
* array containing all of them.
|
||||||
|
*
|
||||||
|
* @experimental **UNSTABLE**: New API, yet to be vetted.
|
||||||
|
*
|
||||||
|
* If step is set, each window will start that many elements after the last
|
||||||
|
* window's start. (Default: 1)
|
||||||
|
*
|
||||||
|
* If partial is set, windows will be generated for the last elements of the
|
||||||
|
* collection, resulting in some undefined values if size is greater than 1.
|
||||||
|
*
|
||||||
|
* @typeParam T The type of the array elements.
|
||||||
|
*
|
||||||
|
* @param iterable The iterable to generate sliding windows from.
|
||||||
|
* @param size The size of the sliding windows.
|
||||||
|
* @param options The options for generating sliding windows.
|
||||||
|
*
|
||||||
|
* @returns An array containing all sliding windows of the given size.
|
||||||
|
*
|
||||||
|
* @example Usage
|
||||||
|
* ```ts
|
||||||
|
* import { slidingWindows } from "@std/collections/unstable-sliding-windows";
|
||||||
|
* import { assertEquals } from "@std/assert";
|
||||||
|
* const numbers = [1, 2, 3, 4, 5];
|
||||||
|
*
|
||||||
|
* const windows = slidingWindows(numbers, 3);
|
||||||
|
* assertEquals(windows, [
|
||||||
|
* [1, 2, 3],
|
||||||
|
* [2, 3, 4],
|
||||||
|
* [3, 4, 5],
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const windowsWithStep = slidingWindows(numbers, 3, { step: 2 });
|
||||||
|
* assertEquals(windowsWithStep, [
|
||||||
|
* [1, 2, 3],
|
||||||
|
* [3, 4, 5],
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* const windowsWithPartial = slidingWindows(numbers, 3, { partial: true });
|
||||||
|
* assertEquals(windowsWithPartial, [
|
||||||
|
* [1, 2, 3],
|
||||||
|
* [2, 3, 4],
|
||||||
|
* [3, 4, 5],
|
||||||
|
* [4, 5],
|
||||||
|
* [5],
|
||||||
|
* ]);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function slidingWindows<T>(
|
||||||
|
iterable: Iterable<T>,
|
||||||
|
size: number,
|
||||||
|
options: SlidingWindowsOptions = {},
|
||||||
|
): T[][] {
|
||||||
|
const { step = 1, partial = false } = options;
|
||||||
|
if (!Number.isInteger(size) || size <= 0) {
|
||||||
|
throw new RangeError(
|
||||||
|
`Cannot create sliding windows: size must be a positive integer, current value is ${size}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(step) || step <= 0) {
|
||||||
|
throw new RangeError(
|
||||||
|
`Cannot create sliding windows: step must be a positive integer, current value is ${step}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const array = Array.isArray(iterable) ? iterable : Array.from(iterable);
|
||||||
|
const len = array.length;
|
||||||
|
const result: T[][] = [];
|
||||||
|
for (let i = 0; i <= len; i += step) {
|
||||||
|
let last = i + size;
|
||||||
|
if (last > len) {
|
||||||
|
last = len;
|
||||||
|
}
|
||||||
|
const window: T[] = array.slice(i, last);
|
||||||
|
if ((partial && window.length) || window.length === size) {
|
||||||
|
result.push(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
335
collections/unstable_sliding_windows_test.ts
Normal file
335
collections/unstable_sliding_windows_test.ts
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { assertEquals, assertThrows } from "@std/assert";
|
||||||
|
import { slidingWindows } from "./unstable_sliding_windows.ts";
|
||||||
|
|
||||||
|
function slidingWindowsTest<T>(
|
||||||
|
input: Parameters<typeof slidingWindows>,
|
||||||
|
expected: T[][],
|
||||||
|
message?: string,
|
||||||
|
) {
|
||||||
|
const actual = slidingWindows(...input);
|
||||||
|
assertEquals(actual, expected, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function slidingWindowsThrowsTest<T>(
|
||||||
|
input: [
|
||||||
|
collection: T[],
|
||||||
|
size: number,
|
||||||
|
config?: { step?: number; partial?: boolean },
|
||||||
|
],
|
||||||
|
ErrorClass: ErrorConstructor,
|
||||||
|
msgIncludes?: string,
|
||||||
|
msg?: string | undefined,
|
||||||
|
) {
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
slidingWindows(...input);
|
||||||
|
},
|
||||||
|
ErrorClass,
|
||||||
|
msgIncludes,
|
||||||
|
msg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles no mutation",
|
||||||
|
fn() {
|
||||||
|
const numbers = [1, 2, 3, 4, 5];
|
||||||
|
slidingWindows(numbers, 3);
|
||||||
|
assertEquals(numbers, [1, 2, 3, 4, 5]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles empty input",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([[], 3], []);
|
||||||
|
slidingWindowsTest([[], 3, {}], []);
|
||||||
|
slidingWindowsTest([[], 3, { step: 2 }], []);
|
||||||
|
slidingWindowsTest([[], 3, { partial: true }], []);
|
||||||
|
slidingWindowsTest([[], 3, { step: 2, partial: true }], []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles default option",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 5], [[1, 2, 3, 4, 5]]);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3],
|
||||||
|
[
|
||||||
|
[1, 2, 3],
|
||||||
|
[2, 3, 4],
|
||||||
|
[3, 4, 5],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 1], [[1], [2], [3], [4], [5]]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles step option",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 5, { step: 2 }], [[1, 2, 3, 4, 5]]);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 2 }],
|
||||||
|
[
|
||||||
|
[1, 2, 3],
|
||||||
|
[3, 4, 5],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 1, { step: 2 }], [[1], [3], [5]]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles partial option",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 5, { partial: true }],
|
||||||
|
[[1, 2, 3, 4, 5], [2, 3, 4, 5], [3, 4, 5], [4, 5], [5]],
|
||||||
|
);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { partial: true }],
|
||||||
|
[[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5], [5]],
|
||||||
|
);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 1, { partial: true }],
|
||||||
|
[[1], [2], [3], [4], [5]],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles step and partial option",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 5, { step: 2, partial: true }],
|
||||||
|
[[1, 2, 3, 4, 5], [3, 4, 5], [5]],
|
||||||
|
);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 2, partial: true }],
|
||||||
|
[[1, 2, 3], [3, 4, 5], [5]],
|
||||||
|
);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 1, { step: 2, partial: true }],
|
||||||
|
[[1], [3], [5]],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles invalid size or step: other than number",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], NaN],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is NaN",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: NaN }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is NaN",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
// @ts-ignore: for test
|
||||||
|
[[1, 2, 3, 4, 5], "invalid"],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is invalid",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
// @ts-ignore: for test
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: "invalid" }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is invalid",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles invalid size or step: not integer number",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 0.5],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is 0.5",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 0.5 }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is 0.5",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 1.5],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is 1.5",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 1.5 }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is 1.5",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles invalid size or step: not positive number",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 0],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is 0",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 0 }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is 0",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], -1],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is -1",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: -1 }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is -1",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles invalid size or step: infinity",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], Number.NEGATIVE_INFINITY],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is -Infinity",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: Number.NEGATIVE_INFINITY }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is -Infinity",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], Number.POSITIVE_INFINITY],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: size must be a positive integer, current value is Infinity",
|
||||||
|
);
|
||||||
|
slidingWindowsThrowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: Number.POSITIVE_INFINITY }],
|
||||||
|
RangeError,
|
||||||
|
"Cannot create sliding windows: step must be a positive integer, current value is Infinity",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles large size",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 100], []);
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 100, { step: 2 }], []);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 100, { step: 2, partial: true }],
|
||||||
|
[[1, 2, 3, 4, 5], [3, 4, 5], [5]],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles large step",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([[1, 2, 3, 4, 5], 3, { step: 100 }], [[1, 2, 3]]);
|
||||||
|
slidingWindowsTest(
|
||||||
|
[[1, 2, 3, 4, 5], 3, { step: 100, partial: true }],
|
||||||
|
[[1, 2, 3]],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "slidingWindows() handles empty Array",
|
||||||
|
fn() {
|
||||||
|
slidingWindowsTest([Array(5), 5], [
|
||||||
|
Array(5),
|
||||||
|
]);
|
||||||
|
slidingWindowsTest([Array(5), 3], [
|
||||||
|
Array(3),
|
||||||
|
Array(3),
|
||||||
|
Array(3),
|
||||||
|
]);
|
||||||
|
slidingWindowsTest([Array(5), 1], [
|
||||||
|
Array(1),
|
||||||
|
Array(1),
|
||||||
|
Array(1),
|
||||||
|
Array(1),
|
||||||
|
Array(1),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("slidingWindows() handles a generator", () => {
|
||||||
|
function* gen() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
yield 4;
|
||||||
|
yield 5;
|
||||||
|
}
|
||||||
|
function* emptyGen() {}
|
||||||
|
slidingWindowsTest([gen(), 5], [[1, 2, 3, 4, 5]]);
|
||||||
|
slidingWindowsTest([gen(), 3], [[1, 2, 3], [2, 3, 4], [3, 4, 5]]);
|
||||||
|
slidingWindowsTest([gen(), 1], [[1], [2], [3], [4], [5]]);
|
||||||
|
slidingWindowsTest([gen(), 3, { partial: true }], [
|
||||||
|
[1, 2, 3],
|
||||||
|
[2, 3, 4],
|
||||||
|
[3, 4, 5],
|
||||||
|
[4, 5],
|
||||||
|
[5],
|
||||||
|
]);
|
||||||
|
slidingWindowsTest([gen(), 3, { step: 2 }], [[1, 2, 3], [3, 4, 5]]);
|
||||||
|
slidingWindowsTest([gen(), 1, { step: 2, partial: true }], [[1], [3], [5]]);
|
||||||
|
|
||||||
|
slidingWindowsTest([emptyGen(), 3], []);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("slidingWindows() handles a string", () => {
|
||||||
|
const str = "12345";
|
||||||
|
slidingWindowsTest([str, 5], [["1", "2", "3", "4", "5"]]);
|
||||||
|
slidingWindowsTest([str, 3], [["1", "2", "3"], ["2", "3", "4"], [
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
]]);
|
||||||
|
slidingWindowsTest([str, 1], [["1"], ["2"], ["3"], ["4"], ["5"]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("slidingWindows() handles a Set", () => {
|
||||||
|
const set = new Set([1, 2, 3, 4, 5]);
|
||||||
|
slidingWindowsTest([set, 5], [[1, 2, 3, 4, 5]]);
|
||||||
|
slidingWindowsTest([set, 3], [[1, 2, 3], [2, 3, 4], [3, 4, 5]]);
|
||||||
|
slidingWindowsTest([set, 1], [[1], [2], [3], [4], [5]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("slidingWindows() handles a Map", () => {
|
||||||
|
const map = new Map([
|
||||||
|
["a", 1],
|
||||||
|
["b", 2],
|
||||||
|
["c", 3],
|
||||||
|
["d", 4],
|
||||||
|
["e", 5],
|
||||||
|
]);
|
||||||
|
slidingWindowsTest([map, 3], [
|
||||||
|
[["a", 1], ["b", 2], ["c", 3]],
|
||||||
|
[["b", 2], ["c", 3], ["d", 4]],
|
||||||
|
[["c", 3], ["d", 4], ["e", 5]],
|
||||||
|
]);
|
||||||
|
slidingWindowsTest([map, 1], [
|
||||||
|
[["a", 1]],
|
||||||
|
[["b", 2]],
|
||||||
|
[["c", 3]],
|
||||||
|
[["d", 4]],
|
||||||
|
[["e", 5]],
|
||||||
|
]);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user