From 9a5ae53d6ac328475b9fb67fab04a1f67c58095e Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Mon, 7 Oct 2024 18:28:06 +1300 Subject: [PATCH 01/11] copy existing sliding sliding_ --- collections/sliding_windows_bench.ts | 19 ++ collections/unstable_sliding_windows.ts | 86 ++++++ collections/unstable_sliding_windows_test.ts | 265 +++++++++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 collections/sliding_windows_bench.ts create mode 100644 collections/unstable_sliding_windows.ts create mode 100644 collections/unstable_sliding_windows_test.ts diff --git a/collections/sliding_windows_bench.ts b/collections/sliding_windows_bench.ts new file mode 100644 index 000000000000..ca17fa79dd9a --- /dev/null +++ b/collections/sliding_windows_bench.ts @@ -0,0 +1,19 @@ +import { slidingWindows } from "./sliding_windows.ts"; +import { slidingWindows as unstableSlidingWindows } from "./unstable_sliding_windows.ts"; + +const array = new Array(1_000).map((_, i) => i); + +console.log(array); +Deno.bench({ + name: "slidingWindows", + fn: () => { + slidingWindows(array, 10); + }, +}); + +Deno.bench({ + name: "(unstable) slidingWindows", + fn: () => { + unstableSlidingWindows(array, 10); + }, +}); diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts new file mode 100644 index 000000000000..087d8da67833 --- /dev/null +++ b/collections/unstable_sliding_windows.ts @@ -0,0 +1,86 @@ +// 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 array of the given size and returns a + * new array containing all of them. + * + * 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 array The array to generate sliding windows from. + * @param size The size of the sliding windows. + * @param options The options for generating sliding windows. + * + * @returns A new array containing all sliding windows of the given size. + * + * @example Usage + * ```ts + * import { slidingWindows } from "@std/collections/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( + array: readonly T[], + size: number, + options: SlidingWindowsOptions = {}, +): T[][] { + const { step = 1, partial = false } = options; + + if ( + !Number.isInteger(size) || !Number.isInteger(step) || size <= 0 || step <= 0 + ) { + throw new RangeError("Both size and step must be positive integer."); + } + + return Array.from( + { length: Math.floor((array.length - (partial ? 1 : size)) / step + 1) }, + (_, i) => array.slice(i * step, i * step + size), + ); +} diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts new file mode 100644 index 000000000000..0202cb76af2a --- /dev/null +++ b/collections/unstable_sliding_windows_test.ts @@ -0,0 +1,265 @@ +// 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( + input: [ + collection: T[], + size: number, + config?: { step?: number; partial?: boolean }, + ], + expected: T[][], + message?: string, +) { + const actual = slidingWindows(...input); + assertEquals(actual, expected, message); +} + +function slidingWindowsThrowsTest( + 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, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: NaN }], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + // @ts-ignore: for test + [[1, 2, 3, 4, 5], "invalid"], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + // @ts-ignore: for test + [[1, 2, 3, 4, 5], 3, { step: "invalid" }], + RangeError, + "Both size and step must be positive integer.", + ); + }, +}); + +Deno.test({ + name: "slidingWindows() handles invalid size or step: not integer number", + fn() { + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 0.5], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: 0.5 }], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 1.5], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: 1.5 }], + RangeError, + "Both size and step must be positive integer.", + ); + }, +}); + +Deno.test({ + name: "slidingWindows() handles invalid size or step: not positive number", + fn() { + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 0], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: 0 }], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], -1], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: -1 }], + RangeError, + "Both size and step must be positive integer.", + ); + }, +}); + +Deno.test({ + name: "slidingWindows() handles invalid size or step: infinity", + fn() { + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], Number.NEGATIVE_INFINITY], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: Number.NEGATIVE_INFINITY }], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], Number.POSITIVE_INFINITY], + RangeError, + "Both size and step must be positive integer.", + ); + slidingWindowsThrowsTest( + [[1, 2, 3, 4, 5], 3, { step: Number.POSITIVE_INFINITY }], + RangeError, + "Both size and step must be positive integer.", + ); + }, +}); + +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)], + ); + }, +}); From 7aafc4da10a8a3fe1e7fbd445a4fb0f82baa6552 Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Mon, 7 Oct 2024 20:21:29 +1300 Subject: [PATCH 02/11] feat(collections/unstable): support `Iterable` argument in `slidingWindows` --- collections/sliding_windows_bench.ts | 46 +++++++++-- collections/unstable_sliding_windows.ts | 87 ++++++++++++++++++-- collections/unstable_sliding_windows_test.ts | 24 +++--- 3 files changed, 133 insertions(+), 24 deletions(-) diff --git a/collections/sliding_windows_bench.ts b/collections/sliding_windows_bench.ts index ca17fa79dd9a..fe58d020ec4d 100644 --- a/collections/sliding_windows_bench.ts +++ b/collections/sliding_windows_bench.ts @@ -1,19 +1,55 @@ import { slidingWindows } from "./sliding_windows.ts"; -import { slidingWindows as unstableSlidingWindows } from "./unstable_sliding_windows.ts"; +import { slidingWindows as unstableSlidingWindows, slidingWindowsIter } from "./unstable_sliding_windows.ts"; -const array = new Array(1_000).map((_, i) => i); +const array = Array.from({ length: 1_000_000 }, () => Math.random()); -console.log(array); Deno.bench({ name: "slidingWindows", + group: "array", fn: () => { - slidingWindows(array, 10); + slidingWindows(array, 3); }, }); + +Deno.bench({ + name: "(unstable) slidingWindows", + group: "array", + fn: () => { + unstableSlidingWindows(array, 3); + }, +}); + +Deno.bench({ + name: "(unstable) slidingWindows iter", + group: "array", + fn: () => { + slidingWindowsIter(array, 3); + }, +}); + + + +function* gen() { + for (let i = 0; i < 1_000; i++) { + yield i; + } +} + + Deno.bench({ name: "(unstable) slidingWindows", + group: "iter", fn: () => { - unstableSlidingWindows(array, 10); + unstableSlidingWindows(gen(), 3); }, }); + +Deno.bench({ + name: "(unstable) slidingWindows iter", + group: "iter", + fn: () => { + slidingWindowsIter(gen(), 3); + }, +}); + diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index 087d8da67833..c276b7e8c2e2 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -67,20 +67,93 @@ export interface SlidingWindowsOptions { * ``` */ export function slidingWindows( - array: readonly T[], + iterable: Iterable, size: number, options: SlidingWindowsOptions = {}, ): T[][] { const { step = 1, partial = false } = options; - if ( - !Number.isInteger(size) || !Number.isInteger(step) || size <= 0 || step <= 0 + !Number.isInteger(size) || + !Number.isInteger(step) || + size <= 0 || + step <= 0 ) { throw new RangeError("Both size and step must be positive integer."); } - return Array.from( - { length: Math.floor((array.length - (partial ? 1 : size)) / step + 1) }, - (_, i) => array.slice(i * step, i * step + size), - ); + 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[] = []; + for (let j = i; j < last; j++) { + window.push(array[j]!); + } + if ((partial && window.length) || window.length === size) { + result.push(window); + } + } + return result; } + +export function slidingWindowsIter( + iterable: Iterable, + size: number, + options: SlidingWindowsOptions = {}, +): T[][] { + const { step = 1, partial = false } = options; + + if ( + !Number.isInteger(size) || + !Number.isInteger(step) || + size <= 0 || + step <= 0 + ) { + throw new RangeError("Both size and step must be positive integer."); + } + const result: T[][] = []; + const window: T[] = []; + + const iterator = iterable[Symbol.iterator](); + + while (true) { + while (window.length < size) { + const next = iterator.next(); + if (next.done) { + if (partial) { + while (window.length > 0) { + result.push(window.slice()); + window.splice(0, step); + } + } + break; + } + window.push(next.value); + } + if (window.length < size) { + break; + } + result.push(window.slice()); + while (window.length < step) { + const next = iterator.next(); + if (next.done) { + break; + } + window.push(next.value); + } + window.splice(0, step); + } + + if (partial) { + while (window.length > 0) { + result.push(window.slice()); + window.splice(0, step); + } + } + + return result; +} \ No newline at end of file diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index 0202cb76af2a..1d68ae754b70 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { assertEquals, assertThrows } from "@std/assert"; -import { slidingWindows } from "./unstable_sliding_windows.ts"; +import { slidingWindowsIter as slidingWindows } from "./unstable_sliding_windows.ts"; function slidingWindowsTest( input: [ @@ -252,14 +252,14 @@ Deno.test({ }, }); -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({ +// 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)], +// ); +// }, +// }); From 8947865d83db124628b39443ed25db1e390655cb Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:01:41 +1300 Subject: [PATCH 03/11] clean up --- collections/deno.json | 1 + collections/sliding_windows_bench.ts | 55 ---------------- collections/unstable_sliding_windows.ts | 68 ++------------------ collections/unstable_sliding_windows_test.ts | 2 +- 4 files changed, 7 insertions(+), 119 deletions(-) delete mode 100644 collections/sliding_windows_bench.ts diff --git a/collections/deno.json b/collections/deno.json index 860ac8f29e8e..03d7373bb28d 100644 --- a/collections/deno.json +++ b/collections/deno.json @@ -49,6 +49,7 @@ "./unstable-chunk": "./unstable_chunk.ts", "./unstable-drop-last-while": "./unstable_drop_last_while.ts", "./unstable-sample": "./unstable_sample.ts", + "./unstable-sliding-windows": "./unstable_sliding_windows.ts", "./unstable-sort-by": "./unstable_sort_by.ts", "./unstable-take-while": "./unstable_take_while.ts", "./unstable-without-all": "./unstable_without_all.ts", diff --git a/collections/sliding_windows_bench.ts b/collections/sliding_windows_bench.ts deleted file mode 100644 index fe58d020ec4d..000000000000 --- a/collections/sliding_windows_bench.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { slidingWindows } from "./sliding_windows.ts"; -import { slidingWindows as unstableSlidingWindows, slidingWindowsIter } from "./unstable_sliding_windows.ts"; - -const array = Array.from({ length: 1_000_000 }, () => Math.random()); - -Deno.bench({ - name: "slidingWindows", - group: "array", - fn: () => { - slidingWindows(array, 3); - }, -}); - - -Deno.bench({ - name: "(unstable) slidingWindows", - group: "array", - fn: () => { - unstableSlidingWindows(array, 3); - }, -}); - -Deno.bench({ - name: "(unstable) slidingWindows iter", - group: "array", - fn: () => { - slidingWindowsIter(array, 3); - }, -}); - - - -function* gen() { - for (let i = 0; i < 1_000; i++) { - yield i; - } -} - - -Deno.bench({ - name: "(unstable) slidingWindows", - group: "iter", - fn: () => { - unstableSlidingWindows(gen(), 3); - }, -}); - -Deno.bench({ - name: "(unstable) slidingWindows iter", - group: "iter", - fn: () => { - slidingWindowsIter(gen(), 3); - }, -}); - diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index c276b7e8c2e2..10ee52e6566a 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -20,8 +20,8 @@ export interface SlidingWindowsOptions { } /** - * Generates sliding views of the given array of the given size and returns a - * new array containing all of them. + * Generates sliding views of the given iterable of the given size and returns an + * array containing all of them. * * If step is set, each window will start that many elements after the last * window's start. (Default: 1) @@ -31,15 +31,15 @@ export interface SlidingWindowsOptions { * * @typeParam T The type of the array elements. * - * @param array The array to generate sliding windows from. + * @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 A new array containing all sliding windows of the given size. + * @returns An array containing all sliding windows of the given size. * * @example Usage * ```ts - * import { slidingWindows } from "@std/collections/sliding-windows"; + * import { slidingWindows } from "@std/collections/unstable-sliding-windows"; * import { assertEquals } from "@std/assert"; * const numbers = [1, 2, 3, 4, 5]; * @@ -99,61 +99,3 @@ export function slidingWindows( } return result; } - -export function slidingWindowsIter( - iterable: Iterable, - size: number, - options: SlidingWindowsOptions = {}, -): T[][] { - const { step = 1, partial = false } = options; - - if ( - !Number.isInteger(size) || - !Number.isInteger(step) || - size <= 0 || - step <= 0 - ) { - throw new RangeError("Both size and step must be positive integer."); - } - const result: T[][] = []; - const window: T[] = []; - - const iterator = iterable[Symbol.iterator](); - - while (true) { - while (window.length < size) { - const next = iterator.next(); - if (next.done) { - if (partial) { - while (window.length > 0) { - result.push(window.slice()); - window.splice(0, step); - } - } - break; - } - window.push(next.value); - } - if (window.length < size) { - break; - } - result.push(window.slice()); - while (window.length < step) { - const next = iterator.next(); - if (next.done) { - break; - } - window.push(next.value); - } - window.splice(0, step); - } - - if (partial) { - while (window.length > 0) { - result.push(window.slice()); - window.splice(0, step); - } - } - - return result; -} \ No newline at end of file diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index 1d68ae754b70..c498e4592c11 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { assertEquals, assertThrows } from "@std/assert"; -import { slidingWindowsIter as slidingWindows } from "./unstable_sliding_windows.ts"; +import { slidingWindows } from "./unstable_sliding_windows.ts"; function slidingWindowsTest( input: [ From dcb9233ede978622ecc0986a417150e3de163af5 Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:12:52 +1300 Subject: [PATCH 04/11] more specific error messages --- collections/unstable_sliding_windows.ts | 15 +++++---- collections/unstable_sliding_windows_test.ts | 32 ++++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index 10ee52e6566a..4893ff45ff7c 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -23,6 +23,8 @@ export interface SlidingWindowsOptions { * 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) * @@ -72,15 +74,12 @@ export function slidingWindows( options: SlidingWindowsOptions = {}, ): T[][] { const { step = 1, partial = false } = options; - if ( - !Number.isInteger(size) || - !Number.isInteger(step) || - size <= 0 || - step <= 0 - ) { - throw new RangeError("Both size and step must be positive integer."); + 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[][] = []; diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index c498e4592c11..1c7e8320a29c 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -129,24 +129,24 @@ Deno.test({ slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], NaN], RangeError, - "Both size and step must be positive integer.", + "Cannot create sliding windows: size must be a positive integer, current value is NaN", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 3, { step: NaN }], RangeError, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is invalid", ); }, }); @@ -157,22 +157,22 @@ Deno.test({ slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 0.5], RangeError, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is 0.5", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 1.5], RangeError, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is 1.5", ); }, }); @@ -183,22 +183,22 @@ Deno.test({ slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 0], RangeError, - "Both size and step must be positive integer.", + "Cannot create sliding windows: size must be a positive integer, current value is 0", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 3, { step: 0 }], RangeError, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is 0", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], -1], RangeError, - "Both size and step must be positive integer.", + "Cannot create sliding windows: size must be a positive integer, current value is -1", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], 3, { step: -1 }], RangeError, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is -1", ); }, }); @@ -209,22 +209,22 @@ Deno.test({ slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], Number.NEGATIVE_INFINITY], RangeError, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is -Infinity", ); slidingWindowsThrowsTest( [[1, 2, 3, 4, 5], Number.POSITIVE_INFINITY], RangeError, - "Both size and step must be positive integer.", + "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, - "Both size and step must be positive integer.", + "Cannot create sliding windows: step must be a positive integer, current value is Infinity", ); }, }); From 318afebe541f0ad6a4b0b62f3008cda162d4f88d Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:19:05 +1300 Subject: [PATCH 05/11] tests --- collections/unstable_sliding_windows_test.ts | 64 +++++++++++++++----- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index 1c7e8320a29c..d2760b528609 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -4,11 +4,7 @@ import { assertEquals, assertThrows } from "@std/assert"; import { slidingWindows } from "./unstable_sliding_windows.ts"; function slidingWindowsTest( - input: [ - collection: T[], - size: number, - config?: { step?: number; partial?: boolean }, - ], + input: Parameters, expected: T[][], message?: string, ) { @@ -252,14 +248,50 @@ Deno.test({ }, }); -// 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({ + name: "slidingWindows() handles empty Array", + fn() { + slidingWindowsTest([Array(5), 5], [[ + undefined, + undefined, + undefined, + undefined, + undefined, + ]]); + slidingWindowsTest([Array(5), 3], [[undefined, undefined, undefined], [ + undefined, + undefined, + undefined, + ], [undefined, undefined, undefined]]); + + slidingWindowsTest( + [Array(5), 1], + [[undefined], [undefined], [undefined], [undefined], [undefined]], + ); + }, +}); + +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], []); +}); From 5e507e46ef85106cf43986b1eea6ed1dbd0463ca Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:22:38 +1300 Subject: [PATCH 06/11] tests --- collections/unstable_sliding_windows_test.ts | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index d2760b528609..b139c561eb96 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -295,3 +295,41 @@ Deno.test("slidingWindows() handles a generator", () => { 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]] + ]); +}) From 10655b30b8283f3c8145885fe2556eb501d6c2e1 Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:24:33 +1300 Subject: [PATCH 07/11] fmt --- collections/unstable_sliding_windows.ts | 12 ++++++++---- collections/unstable_sliding_windows_test.ts | 16 +++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index 4893ff45ff7c..1fc59e56cffd 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -74,11 +74,15 @@ export function slidingWindows( 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(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}`) + 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; diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index b139c561eb96..2f01bbe17e9d 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -296,21 +296,23 @@ Deno.test("slidingWindows() handles a generator", () => { 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, 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([ @@ -330,6 +332,6 @@ Deno.test("slidingWindows() handles a Map", () => { [["b", 2]], [["c", 3]], [["d", 4]], - [["e", 5]] + [["e", 5]], ]); -}) +}); From 04b0e349907527588f23702179be6780e4001485 Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:41:36 +1300 Subject: [PATCH 08/11] use slice --- collections/unstable_sliding_windows.ts | 20 +++++------- collections/unstable_sliding_windows_test.ts | 32 +++++++++----------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index 1fc59e56cffd..25a1885e4ffc 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -74,15 +74,11 @@ export function slidingWindows( 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(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}`, - ); + 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; @@ -92,10 +88,10 @@ export function slidingWindows( if (last > len) { last = len; } - const window: T[] = []; - for (let j = i; j < last; j++) { - window.push(array[j]!); - } + const window: T[] = array.slice(i, last); + // for (let j = i; j < last; j++) { + // window.push(array[j]!); + // } if ((partial && window.length) || window.length === size) { result.push(window); } diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts index 2f01bbe17e9d..3ad75a3c08ee 100644 --- a/collections/unstable_sliding_windows_test.ts +++ b/collections/unstable_sliding_windows_test.ts @@ -251,23 +251,21 @@ Deno.test({ Deno.test({ name: "slidingWindows() handles empty Array", fn() { - slidingWindowsTest([Array(5), 5], [[ - undefined, - undefined, - undefined, - undefined, - undefined, - ]]); - slidingWindowsTest([Array(5), 3], [[undefined, undefined, undefined], [ - undefined, - undefined, - undefined, - ], [undefined, undefined, undefined]]); - - slidingWindowsTest( - [Array(5), 1], - [[undefined], [undefined], [undefined], [undefined], [undefined]], - ); + 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), + ]); }, }); From db5c4452ef340379b49425350e9d75020eecff8e Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Tue, 8 Oct 2024 01:41:45 +1300 Subject: [PATCH 09/11] add bench --- collections/sliding_windows_bench.ts | 28 +++++++++++++++++++++++++ collections/unstable_sliding_windows.ts | 12 +++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 collections/sliding_windows_bench.ts diff --git a/collections/sliding_windows_bench.ts b/collections/sliding_windows_bench.ts new file mode 100644 index 000000000000..dbd0f516f566 --- /dev/null +++ b/collections/sliding_windows_bench.ts @@ -0,0 +1,28 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { slidingWindows } from "./sliding_windows.ts"; +import { slidingWindows as unstableSlidingWindows } from "./unstable_sliding_windows.ts"; + +const arraySize = [10, 100, 1_000, 10_000, 100_000, 1_000_000]; + +for (const len of arraySize) { + const array = Array.from({ length: len }, (_, i) => i); + const group = `${len} elements`; + const size = 7; + + Deno.bench({ + name: "slidingWindows", + group, + fn: () => { + slidingWindows(array, size); + }, + }); + + Deno.bench({ + name: "(unstable) slidingWindows", + group, + baseline: true, + fn: () => { + unstableSlidingWindows(array, size); + }, + }); +} diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index 25a1885e4ffc..c21e59fde393 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -74,11 +74,15 @@ export function slidingWindows( 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(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}`) + 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; From df28f179b1874ef71937f04e579da6f6211cd5c1 Mon Sep 17 00:00:00 2001 From: Liam Tait Date: Sat, 12 Oct 2024 22:14:43 +1300 Subject: [PATCH 10/11] remove benchmark --- collections/sliding_windows_bench.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 collections/sliding_windows_bench.ts diff --git a/collections/sliding_windows_bench.ts b/collections/sliding_windows_bench.ts deleted file mode 100644 index dbd0f516f566..000000000000 --- a/collections/sliding_windows_bench.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { slidingWindows } from "./sliding_windows.ts"; -import { slidingWindows as unstableSlidingWindows } from "./unstable_sliding_windows.ts"; - -const arraySize = [10, 100, 1_000, 10_000, 100_000, 1_000_000]; - -for (const len of arraySize) { - const array = Array.from({ length: len }, (_, i) => i); - const group = `${len} elements`; - const size = 7; - - Deno.bench({ - name: "slidingWindows", - group, - fn: () => { - slidingWindows(array, size); - }, - }); - - Deno.bench({ - name: "(unstable) slidingWindows", - group, - baseline: true, - fn: () => { - unstableSlidingWindows(array, size); - }, - }); -} From 575428871eb9707d3e726d093e6625b7fb4dce70 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Wed, 16 Oct 2024 12:13:32 +0900 Subject: [PATCH 11/11] remove unused code comments --- collections/unstable_sliding_windows.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts index c21e59fde393..26d7889652d1 100644 --- a/collections/unstable_sliding_windows.ts +++ b/collections/unstable_sliding_windows.ts @@ -93,9 +93,6 @@ export function slidingWindows( last = len; } const window: T[] = array.slice(i, last); - // for (let j = i; j < last; j++) { - // window.push(array[j]!); - // } if ((partial && window.length) || window.length === size) { result.push(window); }