diff --git a/collections/chunk.ts b/collections/chunk.ts index bb6c5cfc9cbd..3cfb5db9ce08 100644 --- a/collections/chunk.ts +++ b/collections/chunk.ts @@ -2,11 +2,11 @@ // This module is browser compatible. /** - * Splits the given array into chunks of the given size and returns them. + * Splits the given array into an array of chunks of the given size and returns them. * - * @typeParam T Type of the elements in the input array. + * @typeParam T The type of the elements in the iterable. * - * @param array The array to split into chunks. + * @param iterable The iterable to take elements from. * @param size The size of the chunks. This must be a positive integer. * * @returns An array of chunks of the given size. @@ -37,20 +37,37 @@ * ); * ``` */ -export function chunk(array: readonly T[], size: number): T[][] { +export function chunk( + iterable: Iterable, + size: number, +): T[][] { if (size <= 0 || !Number.isInteger(size)) { throw new RangeError( `Expected size to be an integer greater than 0 but found ${size}`, ); } - const result: T[][] = []; - let index = 0; - while (index < array.length) { - result.push(array.slice(index, index + size)); - index += size; + // Faster path + if (Array.isArray(iterable)) { + let index = 0; + while (index < iterable.length) { + result.push(iterable.slice(index, index + size)); + index += size; + } + return result; } + let chunk: T[] = []; + for (const item of iterable) { + chunk.push(item); + if (chunk.length === size) { + result.push(chunk); + chunk = []; + } + } + if (chunk.length > 0) { + result.push(chunk); + } return result; } diff --git a/collections/chunk_test.ts b/collections/chunk_test.ts index fd650de217c5..445944687f06 100644 --- a/collections/chunk_test.ts +++ b/collections/chunk_test.ts @@ -15,7 +15,7 @@ function chunkTest( const testArray = [1, 2, 3, 4, 5, 6]; Deno.test({ - name: "chunk() handles no mutation", + name: "chunk() does not mutate the input", fn() { const array = [1, 2, 3, 4]; chunk(array, 2); @@ -120,3 +120,113 @@ Deno.test({ ); }, }); + +Deno.test("chunk() handles a generator", () => { + function* gen() { + yield "a"; + yield "b"; + yield "c"; + yield "d"; + } + assertEquals(chunk(gen(), 1), [["a"], ["b"], ["c"], ["d"]], "size = 1"); + assertEquals(chunk(gen(), 2), [["a", "b"], ["c", "d"]], "size = 2"); + assertEquals(chunk(gen(), 3), [["a", "b", "c"], ["d"]], "size = 3"); + assertEquals(chunk(gen(), 4), [["a", "b", "c", "d"]], "size = gen.length"); + assertEquals(chunk(gen(), 5), [["a", "b", "c", "d"]], "size > gen.length"); +}); + +Deno.test("chunk() handles a string", () => { + assertEquals(chunk("abcdefg", 4), [ + ["a", "b", "c", "d"], + ["e", "f", "g"], + ]); +}); + +Deno.test("chunk() handles a Set", () => { + const set = new Set([1, 2, 3, 4, 5, 6]); + assertEquals(chunk(set, 2), [ + [1, 2], + [3, 4], + [5, 6], + ]); +}); + +Deno.test("chunk() handles a Map", () => { + const map = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ["d", 4], + ["e", 5], + ["f", 6], + ]); + assertEquals(chunk(map, 2), [ + [ + ["a", 1], + ["b", 2], + ], + [ + ["c", 3], + ["d", 4], + ], + [ + ["e", 5], + ["f", 6], + ], + ]); +}); + +Deno.test("chunk() handles user-defined iterable", () => { + class MyIterable { + *[Symbol.iterator]() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + yield 6; + } + } + assertEquals(chunk(new MyIterable(), 2), [ + [1, 2], + [3, 4], + [5, 6], + ]); +}); + +Deno.test("chunk() handles a TypedArrays", () => { + const typedArrays = [ + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, + ]; + for (const TypedArray of typedArrays) { + const array = new TypedArray([1, 2, 3, 4, 5, 6]); + assertEquals(chunk(array, 2), [ + [1, 2], + [3, 4], + [5, 6], + ]); + } +}); + +Deno.test("chunk() handles an array with empty slots", () => { + // Regression test for chunk that only allowed Array instances instead of any Iterable. + // This is a special case where an array is filled with empty slots which is a different kind of nothing than null or undefined + // Typed arrays are not affected, as they are filled with 0 instead of empty slots + const arr = new Array(4); + arr[2] = 3; + + const expectedSecondChunk = new Array(2); + expectedSecondChunk[0] = 3; + assertEquals(chunk(arr, 2), [ + new Array(2), + expectedSecondChunk, + ]); +}); diff --git a/collections/deno.json b/collections/deno.json index 0770a08e334a..5ed616f565e9 100644 --- a/collections/deno.json +++ b/collections/deno.json @@ -46,17 +46,7 @@ "./take-last-while": "./take_last_while.ts", "./take-while": "./take_while.ts", "./union": "./union.ts", - "./unstable-chunk": "./unstable_chunk.ts", "./unstable-cycle": "./unstable_cycle.ts", - "./unstable-drop-while": "./unstable_drop_while.ts", - "./unstable-drop-last-while": "./unstable_drop_last_while.ts", - "./unstable-intersect": "./unstable_intersect.ts", - "./unstable-sample": "./unstable_sample.ts", - "./unstable-sliding-windows": "./unstable_sliding_windows.ts", - "./unstable-sort-by": "./unstable_sort_by.ts", - "./unstable-take-last-while": "./unstable_take_last_while.ts", - "./unstable-take-while": "./unstable_take_while.ts", - "./unstable-without-all": "./unstable_without_all.ts", "./unzip": "./unzip.ts", "./without-all": "./without_all.ts", "./zip": "./zip.ts" diff --git a/collections/drop_last_while.ts b/collections/drop_last_while.ts index 9555c9cc36a3..f65b0cb3b788 100644 --- a/collections/drop_last_while.ts +++ b/collections/drop_last_while.ts @@ -2,15 +2,15 @@ // This module is browser compatible. /** - * Returns a new array that drops all elements in the given collection until the + * Returns an array that drops all elements in the given iterable until the * last element that does not match the given predicate. * - * @typeParam T The type of the elements in the input array. + * @typeParam T The type of the elements in the input iterable. * - * @param array The array to drop elements from. + * @param iterable The iterable to drop elements from. * @param predicate The function to test each element for a condition. * - * @returns A new array that drops all elements until the last element that does + * @returns An array that drops all elements until the last element that does * not match the given predicate. * * @example Basic usage @@ -26,11 +26,13 @@ * ``` */ export function dropLastWhile( - array: readonly T[], + iterable: Iterable, predicate: (el: T) => boolean, ): T[] { - let offset = array.length; - while (0 < offset && predicate(array[offset - 1] as T)) offset--; - - return array.slice(0, offset); + const array = Array.isArray(iterable) ? iterable : Array.from(iterable); + let offset = array.length - 1; + while (offset >= 0 && predicate(array[offset]!)) { + offset--; + } + return array.slice(0, offset + 1); } diff --git a/collections/drop_last_while_test.ts b/collections/drop_last_while_test.ts index f42a89077136..ec1e0883fe67 100644 --- a/collections/drop_last_while_test.ts +++ b/collections/drop_last_while_test.ts @@ -10,11 +10,9 @@ Deno.test("dropLastWhile() handles num array", () => { assertEquals(actual, [20]); }); -Deno.test("dropLastWhile() handles no mutation", () => { +Deno.test("dropLastWhile() does not mutate the input array", () => { const array = [1, 2, 3, 4, 5, 6]; - const actual = dropLastWhile(array, (i) => i > 4); - assertEquals(actual, [1, 2, 3, 4]); assertEquals(array, [1, 2, 3, 4, 5, 6]); }); @@ -50,3 +48,35 @@ Deno.test("dropLastWhile() returns empty array when all elements get dropped", ( assertEquals(actual, []); }); + +Deno.test("dropLastWhile() handles a string", () => { + const values = "hello there world"; + const actual = dropLastWhile(values, (i) => i !== " "); + assertEquals(actual, "hello there ".split("")); +}); + +Deno.test("dropLastWhile() handles a Set", () => { + const values = new Set([20, 33, 44]); + const actual = dropLastWhile(values, (i) => i > 30); + assertEquals(actual, [20]); +}); + +Deno.test("dropLastWhile() handles a Map", () => { + const values = new Map([ + ["a", 20], + ["b", 33], + ["c", 44], + ]); + const actual = dropLastWhile(values, ([_k, v]) => v > 30); + assertEquals(actual, [["a", 20]]); +}); + +Deno.test("dropLastWhile() handles a generator", () => { + function* gen() { + yield 20; + yield 33; + yield 44; + } + const actual = dropLastWhile(gen(), (i) => i > 30); + assertEquals(actual, [20]); +}); diff --git a/collections/drop_while.ts b/collections/drop_while.ts index 18703c1604a3..3b345d613f1c 100644 --- a/collections/drop_while.ts +++ b/collections/drop_while.ts @@ -2,15 +2,15 @@ // This module is browser compatible. /** - * Returns a new array that drops all elements in the given collection until the + * Returns an array that drops all elements in the given iterable until the * first element that does not match the given predicate. * - * @typeParam T The type of the elements in the input array. + * @typeParam T The type of the elements in the input iterable. * - * @param array The array to drop elements from. + * @param iterable The iterable to drop elements from. * @param predicate The function to test each element for a condition. * - * @returns A new array that drops all elements until the first element that + * @returns An array that drops all elements until the first element that * does not match the given predicate. * * @example Basic usage @@ -25,15 +25,23 @@ * ``` */ export function dropWhile( - array: readonly T[], + iterable: Iterable, predicate: (el: T) => boolean, ): T[] { - let offset = 0; - const length = array.length; - - while (length > offset && predicate(array[offset] as T)) { - offset++; + if (Array.isArray(iterable)) { + const idx = iterable.findIndex((el) => !predicate(el)); + if (idx === -1) { + return []; + } + return iterable.slice(idx); } - - return array.slice(offset, length); + const array: T[] = []; + let found = false; + for (const item of iterable) { + if (found || !predicate(item)) { + found = true; + array.push(item); + } + } + return array; } diff --git a/collections/drop_while_test.ts b/collections/drop_while_test.ts index 8227adcc9a81..f6eacb907dee 100644 --- a/collections/drop_while_test.ts +++ b/collections/drop_while_test.ts @@ -24,7 +24,7 @@ Deno.test("dropWhile() handles negatives", () => { assertEquals(actual, []); }); -Deno.test("dropWhile() handles no mutation", () => { +Deno.test("dropWhile() doesn't mutate input array", () => { const arr = [1, 2, 3, 4, 5, 6]; const actual = dropWhile(arr, (i) => i !== 4); @@ -40,18 +40,70 @@ Deno.test("dropWhile() handles empty input array returns empty array", () => { assertEquals(actual, []); }); -Deno.test("dropWhile() returns empty array when the last element doesn't match the predicate", () => { +Deno.test("dropWhile() returns the empty array when all elements match the predicate", () => { const arr = [1, 2, 3, 4]; - const actual = dropWhile(arr, (i) => i !== 4); + const actual = dropWhile(arr, (i) => i !== 400); - assertEquals(actual, [4]); + assertEquals(actual, []); }); -Deno.test("dropWhile() returns the same array when all elements match the predicate", () => { - const arr = [1, 2, 3, 4]; +Deno.test("dropWhile() returns full elements when the first element satisfies the predicate", () => { + const arr = [1, 2, 3, 4, 5, 6]; + const actual = dropWhile(arr, (i) => i !== 1); - const actual = dropWhile(arr, (i) => i !== 400); + assertEquals(actual, [1, 2, 3, 4, 5, 6]); +}); + +Deno.test("dropWhile() with (i) => i + 2 !== 6", () => { + const arr = [1, 2, 3, 4, 5, 6]; + const actual = dropWhile(arr, (i) => i + 2 !== 6); + + assertEquals(actual, [4, 5, 6]); +}); + +Deno.test("dropWhile() returns empty array when the input is empty", () => { + const arr: number[] = []; + + const actual = dropWhile(arr, (i) => i > 4); assertEquals(actual, []); }); + +Deno.test("dropWhile() handles a generator", () => { + function* gen() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + yield 6; + } + + const actual = dropWhile(gen(), (i) => i !== 4); + assertEquals(actual, [4, 5, 6]); +}); + +Deno.test("dropWhile() handles a Set", () => { + const set = new Set([1, 2, 3, 4, 5, 6]); + const actual = dropWhile(set, (i) => i !== 4); + assertEquals(actual, [4, 5, 6]); +}); + +Deno.test("dropWhile() handles a Map", () => { + const map = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ["d", 4], + ["e", 5], + ["f", 6], + ]); + + const actual = dropWhile(map, ([_k, v]) => v !== 4); + assertEquals(actual, [ + ["d", 4], + ["e", 5], + ["f", 6], + ]); +}); diff --git a/collections/intersect.ts b/collections/intersect.ts index 31be94250be5..eeca13b7e2e0 100644 --- a/collections/intersect.ts +++ b/collections/intersect.ts @@ -3,14 +3,14 @@ /** * Returns all distinct elements that appear at least once in each of the given - * arrays. + * iterables. * - * @typeParam T The type of the elements in the input arrays. + * @typeParam T The type of the elements in the input iterables. * - * @param arrays The arrays to intersect. + * @param iterables The iterables to intersect. * * @returns An array of distinct elements that appear at least once in each of - * the given arrays. + * the given iterables. * * @example Basic usage * ```ts @@ -24,12 +24,13 @@ * assertEquals(commonInterests, ["Cooking", "Music"]); * ``` */ -export function intersect(...arrays: (readonly T[])[]): T[] { - const [array, ...otherArrays] = arrays; - let set = new Set(array); - for (const array of otherArrays) { - set = set.intersection(new Set(array)); - if (set.size === 0) break; +export function intersect(...iterables: Iterable[]): T[] { + const [iterable, ...otherIterables] = iterables; + let set = new Set(iterable); + if (set.size === 0) return []; + for (const iterable of otherIterables) { + set = set.intersection(new Set(iterable)); + if (set.size === 0) return []; } return [...set]; } diff --git a/collections/intersect_test.ts b/collections/intersect_test.ts index 3b12722aad36..a82c2523e55b 100644 --- a/collections/intersect_test.ts +++ b/collections/intersect_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. -import { assertEquals } from "@std/assert"; +import { assert, assertEquals } from "@std/assert"; import { intersect } from "./intersect.ts"; function intersectTest( @@ -27,14 +27,16 @@ Deno.test({ Deno.test({ name: "intersect() handles empty input", fn() { - intersectTest([], []); + const actual = intersect(); + assertEquals(actual, []); }, }); Deno.test({ name: "intersect() handles empty arrays", fn() { - intersectTest([[], []], []); + const actual = intersect([], []); + assertEquals(actual, []); }, }); @@ -139,3 +141,50 @@ Deno.test({ ], []); }, }); + +// If you are using sets using {@linkcode Set.prototype.intersection} directly is more efficient. +Deno.test("intersect() handles sets", () => { + const a = new Set([1, 2, 3, 4]); + const b = new Set([2, 3]); + assertEquals(intersect(a, b), [2, 3]); +}); + +Deno.test("intersect() handles iterables of different types", () => { + const arr = [1, 2, 3, 4, 5, 6]; + const set = new Set([1, 2, 3, 4, 5]); + function* gen() { + yield 1; + yield 2; + yield 3; + } + const iterable = { + *[Symbol.iterator]() { + yield 3; + yield 6; + }, + }; + assertEquals(intersect(arr, set, gen(), iterable), [3]); +}); + +Deno.test("intersect() handles iterables with no mutation", () => { + const a = [1, 2, 3, 4]; + const b = new Set([2, 3]); + intersect(a, b); + assertEquals(a, [1, 2, 3, 4]); + assert(b.has(2)); + assert(b.has(3)); +}); + +Deno.test("intersect() handles generators", () => { + function* gen() { + yield 1; + yield 2; + yield 3; + yield 4; + } + function* gen2() { + yield 2; + yield 3; + } + assertEquals(intersect(gen(), gen2()), [2, 3]); +}); diff --git a/collections/sample.ts b/collections/sample.ts index 0fd36f946f7b..07206e287b4d 100644 --- a/collections/sample.ts +++ b/collections/sample.ts @@ -2,22 +2,13 @@ // This module is browser compatible. /** - * Produces a random number between the inclusive `lower` and `upper` bounds. - */ -function randomInteger(lower: number, upper: number): number { - return lower + Math.floor(Math.random() * (upper - lower + 1)); -} - -/** - * Returns a random element from the given array. + * Returns a random element from the given iterable. * - * @typeParam T The type of the elements in the array. - * @typeParam O The type of the accumulator. + * @typeParam T The type of the elements in the iterable. * - * @param array The array to sample from. + * @param iterable The iterable to sample from. * - * @returns A random element from the given array, or `undefined` if the array - * is empty. + * @returns A random element from the given iterable, or `undefined` if the iterable has no elements. * * @example Basic usage * ```ts @@ -30,7 +21,17 @@ function randomInteger(lower: number, upper: number): number { * assertArrayIncludes(numbers, [random]); * ``` */ -export function sample(array: readonly T[]): T | undefined { - const length = array.length; - return length ? array[randomInteger(0, length - 1)] : undefined; +export function sample(iterable: Iterable): T | undefined { + let array: readonly T[]; + if (Array.isArray(iterable)) { + array = iterable; + } else { + array = Array.from(iterable); + } + const length: number = array.length; + if (length === 0) { + return undefined; + } + const randomIndex = Math.floor(Math.random() * length); + return array[randomIndex]; } diff --git a/collections/sample_test.ts b/collections/sample_test.ts index e1d617084fe3..fc51534da86c 100644 --- a/collections/sample_test.ts +++ b/collections/sample_test.ts @@ -4,7 +4,7 @@ import { assert, assertEquals } from "@std/assert"; import { sample } from "./sample.ts"; Deno.test({ - name: "sample() handles no mutation", + name: "sample() does not mutate input", fn() { const array = ["a", "abc", "ba"]; sample(array); @@ -49,3 +49,82 @@ Deno.test({ assert(actual && input.includes(actual)); }, }); + +Deno.test("sample() returns the first element if there is only one element", () => { + const input = [1]; + const actual = sample(input); + assertEquals(actual, 1); +}); + +Deno.test("sample() handles a generator", () => { + function* gen() { + yield 1; + yield 2; + yield 3; + } + const actual = sample(gen()); + assert(actual !== undefined); + assert(Array.from(gen()).includes(actual)); +}); + +Deno.test("sample() handles a Set", () => { + const input = new Set([1, 2, 3]); + const actual = sample(input); + assert(actual !== undefined); + assert(input.has(actual)); +}); + +Deno.test("sample() handles a Map", () => { + const input = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], + ]); + const actual = sample(input); + assert(actual !== undefined); + assert(Array.isArray(actual)); + assert(actual.length === 2); + assert(input.has(actual[0])); + assert(input.get(actual[0]) === actual[1]); +}); + +Deno.test("sample() handles TypedArrays", () => { + const typedArrays = [ + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, + ]; + for (const TypedArray of typedArrays) { + const input = new TypedArray([1, 2, 3]); + const actual = sample(input); + assert(actual !== undefined); + assert(input.includes(actual)); + } +}); + +Deno.test("sample() handles a string", () => { + const input = "abc"; + const actual = sample("abc"); + assert(actual !== undefined); + assert(actual.length === 1); + assert(input.includes(actual)); +}); + +Deno.test("sample() handles user-defined iterable", () => { + const iterable = { + *[Symbol.iterator]() { + yield "a"; + yield "b"; + yield "c"; + }, + }; + const actual = sample(iterable); + assert(actual !== undefined); + assert(["a", "b", "c"].includes(actual)); +}); diff --git a/collections/sliding_windows.ts b/collections/sliding_windows.ts index a46a3c74ae1a..87f3ad5bb40c 100644 --- a/collections/sliding_windows.ts +++ b/collections/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,11 +31,11 @@ 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 @@ -67,20 +67,33 @@ 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 - ) { - 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}`, + ); } - - return Array.from( - { length: Math.floor((array.length - (partial ? 1 : size)) / step + 1) }, - (_, i) => array.slice(i * step, i * step + 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; } diff --git a/collections/sliding_windows_test.ts b/collections/sliding_windows_test.ts index ff1563d6c059..af32ae35bba6 100644 --- a/collections/sliding_windows_test.ts +++ b/collections/sliding_windows_test.ts @@ -4,18 +4,13 @@ import { assertEquals, assertThrows } from "@std/assert"; import { slidingWindows } from "./sliding_windows.ts"; function slidingWindowsTest( - input: [ - collection: T[], - size: number, - config?: { step?: number; partial?: boolean }, - ], + input: Parameters, expected: T[][], message?: string, ) { const actual = slidingWindows(...input); assertEquals(actual, expected, message); } - function slidingWindowsThrowsTest( input: [ collection: T[], @@ -149,24 +144,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", ); }, }); @@ -177,22 +172,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", ); }, }); @@ -203,22 +198,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", ); }, }); @@ -229,22 +224,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", ); }, }); @@ -294,3 +289,68 @@ Deno.test({ ]); }, }); + +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]], + ]); +}); diff --git a/collections/sort_by.ts b/collections/sort_by.ts index 82c6cd9a09b8..a9c2d1b474d3 100644 --- a/collections/sort_by.ts +++ b/collections/sort_by.ts @@ -20,18 +20,16 @@ export type SortByOptions = { * element. Ascending or descending order can be specified through the `order` * option. By default, the elements are sorted in ascending order. * - * Note: If you want to process any iterable, use the new version of - * `sortBy` from `@std/collections/unstable-sort-by`. + * @typeParam T The type of the iterator elements. + * @typeParam U The type of the selected values. * - * @typeParam T The type of the array elements. - * - * @param array The array to sort. + * @param iterator The iterator to sort. * @param selector The selector function to get the value to sort by. * @param options The options for sorting. * * @returns A new array containing all elements sorted by the selector. * - * @example Usage + * @example Usage with numbers * ```ts * import { sortBy } from "@std/collections/sort-by"; * import { assertEquals } from "@std/assert"; @@ -57,27 +55,8 @@ export type SortByOptions = { * { name: "John", age: 23 }, * ]); * ``` - */ -export function sortBy( - array: readonly T[], - selector: (el: T) => number, - options?: SortByOptions, -): T[]; -/** - * Returns all elements in the given collection, sorted by their result using - * the given selector. The selector function is called only once for each - * element. Ascending or descending order can be specified through the `order` - * option. By default, the elements are sorted in ascending order. - * - * @typeParam T The type of the array elements. - * - * @param array The array to sort. - * @param selector The selector function to get the value to sort by. - * @param options The options for sorting. * - * @returns A new array containing all elements sorted by the selector. - * - * @example Usage + * @example Usage with strings * ```ts * import { sortBy } from "@std/collections/sort-by"; * import { assertEquals } from "@std/assert"; @@ -95,27 +74,8 @@ export function sortBy( * { name: "Kim" }, * ]); * ``` - */ -export function sortBy( - array: readonly T[], - selector: (el: T) => string, - options?: SortByOptions, -): T[]; -/** - * Returns all elements in the given collection, sorted by their result using - * the given selector. The selector function is called only once for each - * element. Ascending or descending order can be specified through the `order` - * option. By default, the elements are sorted in ascending order. - * - * @typeParam T The type of the array elements. - * - * @param array The array to sort. - * @param selector The selector function to get the value to sort by. - * @param options The options for sorting. - * - * @returns A new array containing all elements sorted by the selector. * - * @example Usage + * @example Usage with bigints * ```ts * import { sortBy } from "@std/collections/sort-by"; * import { assertEquals } from "@std/assert"; @@ -134,28 +94,8 @@ export function sortBy( * { name: "Kim", age: 42n }, * ]); * ``` - */ - -export function sortBy( - array: readonly T[], - selector: (el: T) => bigint, - options?: SortByOptions, -): T[]; -/** - * Returns all elements in the given collection, sorted by their result using - * the given selector. The selector function is called only once for each - * element. Ascending or descending order can be specified through the `order` - * option. By default, the elements are sorted in ascending order. - * - * @typeParam T The type of the array elements. * - * @param array The array to sort. - * @param selector The selector function to get the value to sort by. - * @param options The options for sorting. - * - * @returns A new array containing all elements sorted by the selector. - * - * @example Usage + * @example Usage with Date objects * ```ts * import { sortBy } from "@std/collections/sort-by"; * import { assertEquals } from "@std/assert"; @@ -176,12 +116,7 @@ export function sortBy( * ``` */ export function sortBy( - array: readonly T[], - selector: (el: T) => Date, - options?: SortByOptions, -): T[]; -export function sortBy( - array: readonly T[], + iterator: Iterable, selector: | ((el: T) => number) | ((el: T) => string) @@ -189,31 +124,22 @@ export function sortBy( | ((el: T) => Date), options?: SortByOptions, ): T[] { - const len = array.length; - const indexes = new Array(len); - const selectors = new Array | null>(len); - const order = options?.order ?? "asc"; + const array: { value: T; selected: string | number | bigint | Date }[] = []; - array.forEach((element, index) => { - indexes[index] = index; - const selected = selector(element); - selectors[index] = Number.isNaN(selected) ? null : selected; - }); + for (const item of iterator) { + array.push({ value: item, selected: selector(item) }); + } - indexes.sort((ai, bi) => { - let a = selectors[ai]!; - let b = selectors[bi]!; - if (order === "desc") { - [a, b] = [b, a]; - } - if (a === null) return 1; - if (b === null) return -1; - return a > b ? 1 : a < b ? -1 : 0; - }); + array.sort((oa, ob) => { + const a = oa.selected; + const b = ob.selected; + const order = options?.order === "desc" ? -1 : 1; - for (let i = 0; i < len; i++) { - (indexes as unknown as T[])[i] = array[indexes[i]!] as T; - } + if (Number.isNaN(a)) return order; + if (Number.isNaN(b)) return -order; + + return order * (a > b ? 1 : a < b ? -1 : 0); + }); - return indexes as unknown as T[]; + return array.map((item) => item.value); } diff --git a/collections/sort_by_test.ts b/collections/sort_by_test.ts index 5f2c84e4adfd..3260fa67caae 100644 --- a/collections/sort_by_test.ts +++ b/collections/sort_by_test.ts @@ -2,7 +2,6 @@ import { assertEquals } from "@std/assert"; import { sortBy } from "./sort_by.ts"; -import { sortBy as unstableSortBy } from "./unstable_sort_by.ts"; Deno.test({ name: "sortBy() handles no mutation", @@ -224,235 +223,16 @@ Deno.test({ }); Deno.test({ - name: "(unstable) sortBy() handles no mutation", - fn() { - const array = ["a", "abc", "ba"]; - unstableSortBy(array, (it) => it.length); - - assertEquals(array, ["a", "abc", "ba"]); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() calls the selector function once", - fn() { - let callCount = 0; - const array = [0, 1, 2]; - unstableSortBy(array, (it) => { - callCount++; - return it; - }); - - assertEquals(callCount, array.length); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles empty input", - fn() { - assertEquals(unstableSortBy([], () => 5), []); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles identity selector", - fn() { - assertEquals(unstableSortBy([2, 3, 1], (it) => it), [1, 2, 3]); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles stable sort", - fn() { - assertEquals( - unstableSortBy([ - { id: 1, date: "February 1, 2022" }, - { id: 2, date: "December 17, 1995" }, - { id: 3, date: "June 12, 2012" }, - { id: 4, date: "December 17, 1995" }, - { id: 5, date: "June 12, 2012" }, - ], (it) => new Date(it.date)), - [ - { id: 2, date: "December 17, 1995" }, - { id: 4, date: "December 17, 1995" }, - { id: 3, date: "June 12, 2012" }, - { id: 5, date: "June 12, 2012" }, - { id: 1, date: "February 1, 2022" }, - ], - ); - - assertEquals( - unstableSortBy([ - { id: 1, str: "c" }, - { id: 2, str: "a" }, - { id: 3, str: "b" }, - { id: 4, str: "a" }, - { id: 5, str: "b" }, - ], (it) => it.str), - [ - { id: 2, str: "a" }, - { id: 4, str: "a" }, - { id: 3, str: "b" }, - { id: 5, str: "b" }, - { id: 1, str: "c" }, - ], - ); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles special number values", - fn() { - assertEquals( - unstableSortBy([ - 1, - Number.POSITIVE_INFINITY, - 2, - Number.NEGATIVE_INFINITY, - 3, - Number.NaN, - 4, - Number.NaN, - ], (it) => it), - [ - Number.NEGATIVE_INFINITY, - 1, - 2, - 3, - 4, - Number.POSITIVE_INFINITY, - Number.NaN, - Number.NaN, - ], - ); - - assertEquals( - unstableSortBy([ - Number.NaN, - 1, - Number.POSITIVE_INFINITY, - Number.NaN, - 7, - Number.NEGATIVE_INFINITY, - Number.NaN, - 2, - 6, - 5, - 9, - ], (it) => it), - [ - Number.NEGATIVE_INFINITY, - 1, - 2, - 5, - 6, - 7, - 9, - Number.POSITIVE_INFINITY, - Number.NaN, - Number.NaN, - Number.NaN, - ], - ); - - // Test that NaN sort is stable. - const nanArray = [ - { id: 1, nan: Number.NaN }, - { id: 2, nan: Number.NaN }, - { id: 3, nan: Number.NaN }, - { id: 4, nan: Number.NaN }, - ]; - assertEquals(unstableSortBy(nanArray, ({ nan }) => nan), nanArray); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles sortings", - fn() { - const testArray = [ - { name: "benchmark", stage: 3 }, - { name: "test", stage: 2 }, - { name: "build", stage: 1 }, - { name: "deploy", stage: 4 }, - ]; - - assertEquals(unstableSortBy(testArray, (it) => it.stage), [ - { name: "build", stage: 1 }, - { name: "test", stage: 2 }, - { name: "benchmark", stage: 3 }, - { name: "deploy", stage: 4 }, - ]); - - assertEquals(unstableSortBy(testArray, (it) => it.name), [ - { name: "benchmark", stage: 3 }, - { name: "build", stage: 1 }, - { name: "deploy", stage: 4 }, - { name: "test", stage: 2 }, - ]); - - assertEquals( - unstableSortBy([ - "9007199254740999", - "9007199254740991", - "9007199254740995", - ], (it) => BigInt(it)), - [ - "9007199254740991", - "9007199254740995", - "9007199254740999", - ], - ); - - assertEquals( - unstableSortBy([ - "February 1, 2022", - "December 17, 1995", - "June 12, 2012", - ], (it) => new Date(it)), - [ - "December 17, 1995", - "June 12, 2012", - "February 1, 2022", - ], - ); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() handles desc ordering", - fn() { - assertEquals( - unstableSortBy( - [ - "January 27, 1995", - "November 26, 2020", - "June 17, 1952", - "July 15, 1993", - ], - (it) => new Date(it), - { order: "desc" }, - ), - [ - "November 26, 2020", - "January 27, 1995", - "July 15, 1993", - "June 17, 1952", - ], - ); - }, -}); - -Deno.test({ - name: "(unstable) sortBy() works with iterators", + name: "sortBy() works with iterators", fn() { const set = new Set([10, 312, 99, 5.45, 100, -3, 4.6]); assertEquals( - unstableSortBy(set, (it) => it), + sortBy(set, (it) => it), [-3, 4.6, 5.45, 10, 99, 100, 312], ); assertEquals( - unstableSortBy(set, (it) => it, { order: "desc" }), + sortBy(set, (it) => it, { order: "desc" }), [312, 100, 99, 10, 5.45, 4.6, -3], ); @@ -462,12 +242,12 @@ Deno.test({ ["b", 3], ]); - assertEquals(unstableSortBy(map, (it) => it[0]), [ + assertEquals(sortBy(map, (it) => it[0]), [ ["a", 2], ["b", 3], ["c", 1], ]); - assertEquals(unstableSortBy(map, (it) => it[1], { order: "desc" }), [ + assertEquals(sortBy(map, (it) => it[1], { order: "desc" }), [ ["b", 3], ["a", 2], ["c", 1], diff --git a/collections/take_last_while.ts b/collections/take_last_while.ts index ede00b95879c..5d11ef28113a 100644 --- a/collections/take_last_while.ts +++ b/collections/take_last_while.ts @@ -2,16 +2,16 @@ // This module is browser compatible. /** - * Returns all elements in the given array after the last element that does not + * Returns all elements in the given iterable after the last element that does not * match the given predicate. * - * @typeParam T The type of the array elements. + * @typeParam T The type of the iterable elements. * - * @param array The array to take elements from. + * @param iterable The iterable to take elements from. * @param predicate The predicate function to determine if an element should be * included. * - * @returns A new array containing all elements after the last element that does + * @returns An array containing all elements after the last element that does * not match the predicate. * * @example Basic usage @@ -20,18 +20,28 @@ * import { assertEquals } from "@std/assert"; * * const numbers = [1, 2, 3, 4, 5, 6]; - * * const result = takeLastWhile(numbers, (number) => number > 4); - * * assertEquals(result, [5, 6]); * ``` */ export function takeLastWhile( - array: readonly T[], + iterable: Iterable, predicate: (el: T) => boolean, ): T[] { - let offset = array.length; - while (0 < offset && predicate(array[offset - 1] as T)) offset--; - - return array.slice(offset, array.length); + if (Array.isArray(iterable)) { + let offset = iterable.length; + while (0 < offset && predicate(iterable[offset - 1] as T)) { + offset--; + } + return iterable.slice(offset); + } + const result: T[] = []; + for (const el of iterable) { + if (predicate(el)) { + result.push(el); + } else { + result.length = 0; + } + } + return result; } diff --git a/collections/take_last_while_test.ts b/collections/take_last_while_test.ts index 5fdc1cab5776..74557b91f69b 100644 --- a/collections/take_last_while_test.ts +++ b/collections/take_last_while_test.ts @@ -55,3 +55,64 @@ Deno.test("takeLastWhile() returns the same array when all elements match the pr assertEquals(actual, [1, 2, 3, 4]); }); + +Deno.test("takeLastWhile() handles generator", () => { + function* gen() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + yield 6; + } + const actual = takeLastWhile(gen(), (i) => i !== 4); + assertEquals(actual, [5, 6]); +}); + +Deno.test("takeLastWhile() returns empty array when the last generator element does not match the predicate", () => { + function* gen() { + yield 1; + yield 2; + yield 3; + yield 4; + } + + const actual = takeLastWhile(gen(), (i) => i !== 4); + assertEquals(actual, []); +}); + +Deno.test("takeLastWhile() returns the same array when all elements match the predicate", () => { + function* gen(): Generator { + yield 1; + yield 2; + yield 3; + yield 4; + } + const actual = takeLastWhile(gen(), (i) => i !== 400); + assertEquals(actual, [1, 2, 3, 4]); +}); + +Deno.test("takeLastWhile() empty generator returns empty array", () => { + function* gen(): Generator {} + const actual = takeLastWhile(gen(), (i) => i > 4); + assertEquals(actual, []); +}); + +Deno.test("takeLastWhile() gets from last matching element from an array", () => { + const arr = [1, 2, 3, 4, 5, 6]; + const actual = takeLastWhile(arr, (i) => i !== 2 && i !== 4); + assertEquals(actual, [5, 6]); +}); + +Deno.test("takeLastWhile() gets from last matching element from a generator", () => { + function* gen(): Generator { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + yield 6; + } + const actual = takeLastWhile(gen(), (i) => i !== 2 && i !== 4); + assertEquals(actual, [5, 6]); +}); diff --git a/collections/take_while.ts b/collections/take_while.ts index fce2eb918484..ba9b51cadf6a 100644 --- a/collections/take_while.ts +++ b/collections/take_while.ts @@ -5,16 +5,13 @@ * Returns all elements in the given collection until the first element that * does not match the given predicate. * - * Note: If you want to process any iterable, use the new version of - * `takeWhile` from `@std/collections/unstable-take-while`. + * @typeParam T The type of the elements in the iterable. * - * @typeParam T The type of the array elements. - * - * @param array The array to take elements from. + * @param iterable The iterable to take elements from. * @param predicate The predicate function to determine if an element should be * included. * - * @returns A new array containing all elements until the first element that + * @returns An array containing all elements until the first element that * does not match the predicate. * * @example Basic usage @@ -30,15 +27,15 @@ * ``` */ export function takeWhile( - array: readonly T[], + iterable: Iterable, predicate: (el: T) => boolean, ): T[] { - let offset = 0; - const length = array.length; - - while (length > offset && predicate(array[offset] as T)) { - offset++; + const result: T[] = []; + for (const element of iterable) { + if (!predicate(element)) { + break; + } + result.push(element); } - - return array.slice(0, offset); + return result; } diff --git a/collections/take_while_test.ts b/collections/take_while_test.ts index 1f8fc72d0e12..ff27e261b9e3 100644 --- a/collections/take_while_test.ts +++ b/collections/take_while_test.ts @@ -2,7 +2,6 @@ import { assertEquals } from "@std/assert"; import { takeWhile } from "./take_while.ts"; -import { takeWhile as unstableTakeWhile } from "./unstable_take_while.ts"; Deno.test("takeWhile() handles num array", () => { const arr = [1, 2, 3, 4, 5, 6]; @@ -57,60 +56,7 @@ Deno.test("takeWhile() returns the same array when all elements match the predic assertEquals(actual, [1, 2, 3, 4]); }); -Deno.test("(unstable) takeWhile() handles num array", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = unstableTakeWhile(arr, (i) => i !== 4); - - assertEquals(actual, [1, 2, 3]); -}); - -Deno.test("(unstable) takeWhile() adds two to each num in predicate", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = unstableTakeWhile(arr, (i) => i + 2 !== 6); - - assertEquals(actual, [1, 2, 3]); -}); - -Deno.test("(unstable) takeWhile() handles negatives", () => { - const arr = [-1, -2, -3, -4, -5, -6]; - - const actual = unstableTakeWhile(arr, (i) => i > -4); - assertEquals(actual, [-1, -2, -3]); -}); - -Deno.test("(unstable) takeWhile() handles no mutation", () => { - const arr = [-1, -2, -3, -4, -5, -6]; - - const actual = unstableTakeWhile(arr, (i) => i > -4); - assertEquals(actual, [-1, -2, -3]); - assertEquals(arr, [-1, -2, -3, -4, -5, -6]); -}); - -Deno.test("(unstable) takeWhile() handles empty input array returns empty array", () => { - const arr: number[] = []; - - const actual = unstableTakeWhile(arr, (i) => i > 4); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeWhile() returns empty array when the first element doesn't match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = unstableTakeWhile(arr, (i) => i !== 1); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeWhile() returns the same array when all elements match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = unstableTakeWhile(arr, (i) => i !== 400); - - assertEquals(actual, [1, 2, 3, 4]); -}); - -Deno.test("(unstable) takeWhile() handles a generator", () => { +Deno.test("takeWhile() handles a generator", () => { function* infiniteCount() { let count = 0; while (true) { @@ -118,17 +64,17 @@ Deno.test("(unstable) takeWhile() handles a generator", () => { yield count; } } - const actual = unstableTakeWhile(infiniteCount(), (i) => i !== 4); + const actual = takeWhile(infiniteCount(), (i) => i !== 4); assertEquals(actual, [1, 2, 3]); }); -Deno.test("(unstable) takeWhile() handles a Set", () => { +Deno.test("takeWhile() handles a Set", () => { const set = new Set([1, 2, 3, 4, 5, 6]); - const actual = unstableTakeWhile(set, (i) => i !== 4); + const actual = takeWhile(set, (i) => i !== 4); assertEquals(actual, [1, 2, 3]); }); -Deno.test("(unstable) takeWhile() handles a Map", () => { +Deno.test("takeWhile() handles a Map", () => { const map = new Map([ ["a", 1], ["b", 2], @@ -137,7 +83,7 @@ Deno.test("(unstable) takeWhile() handles a Map", () => { ["e", 5], ["f", 6], ]); - const actual = unstableTakeWhile(map, ([, i]) => i !== 4); + const actual = takeWhile(map, ([, i]) => i !== 4); assertEquals(actual, [ ["a", 1], ["b", 2], diff --git a/collections/unstable_chunk.ts b/collections/unstable_chunk.ts deleted file mode 100644 index f10f84a7b02a..000000000000 --- a/collections/unstable_chunk.ts +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Splits the given array into an array of chunks of the given size and returns them. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the iterable. - * - * @param iterable The iterable to take elements from. - * @param size The size of the chunks. This must be a positive integer. - * - * @returns An array of chunks of the given size. - * - * @example Basic usage - * ```ts - * import { chunk } from "@std/collections/unstable-chunk"; - * import { assertEquals } from "@std/assert"; - * - * const words = [ - * "lorem", - * "ipsum", - * "dolor", - * "sit", - * "amet", - * "consetetur", - * "sadipscing", - * ]; - * const chunks = chunk(words, 3); - * - * assertEquals( - * chunks, - * [ - * ["lorem", "ipsum", "dolor"], - * ["sit", "amet", "consetetur"], - * ["sadipscing"], - * ], - * ); - * ``` - */ -export function chunk( - iterable: Iterable, - size: number, -): T[][] { - if (size <= 0 || !Number.isInteger(size)) { - throw new RangeError( - `Expected size to be an integer greater than 0 but found ${size}`, - ); - } - const result: T[][] = []; - - // Faster path - if (Array.isArray(iterable)) { - let index = 0; - while (index < iterable.length) { - result.push(iterable.slice(index, index + size)); - index += size; - } - return result; - } - - let chunk: T[] = []; - for (const item of iterable) { - chunk.push(item); - if (chunk.length === size) { - result.push(chunk); - chunk = []; - } - } - if (chunk.length > 0) { - result.push(chunk); - } - return result; -} diff --git a/collections/unstable_chunk_test.ts b/collections/unstable_chunk_test.ts deleted file mode 100644 index f115867ee404..000000000000 --- a/collections/unstable_chunk_test.ts +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assertEquals, assertThrows } from "@std/assert"; -import { chunk } from "./unstable_chunk.ts"; - -Deno.test("(unstable) chunk() does not mutate the input", () => { - const array = [1, 2, 3, 4]; - chunk(array, 2); - assertEquals(array, [1, 2, 3, 4], "Input array should not be mutated"); -}); - -Deno.test("(unstable) chunk() throws on non naturals", () => { - assertThrows( - () => chunk([], +.5), - RangeError, - "Expected size to be an integer greater than 0 but found 0.5", - ); - assertThrows( - () => chunk([], -4.7), - RangeError, - "Expected size to be an integer greater than 0 but found -4.7", - ); - assertThrows( - () => chunk([], -2), - RangeError, - "Expected size to be an integer greater than 0 but found -2", - ); - assertThrows( - () => chunk([], +0), - RangeError, - "Expected size to be an integer greater than 0 but found 0", - ); - assertThrows( - () => chunk([], -0), - RangeError, - "Expected size to be an integer greater than 0 but found 0", - ); -}); - -Deno.test("(unstable) chunk() returns empty array when input is empty", () => { - const actual = chunk([], 1); - assertEquals(actual, [], "Empty input should return empty array"); -}); - -Deno.test({ - name: "(unstable) chunk() handles single element chunks", - fn() { - assertEquals(chunk([1, 2, 3, 4, 5, 6], 1), [[1], [2], [3], [4], [5], [6]]); - assertEquals(chunk(["foo"], 1), [["foo"]]); - assertEquals(chunk([null], 1), [[null]]); - assertEquals(chunk([undefined], 1), [[undefined]]); - }, -}); - -Deno.test("(unstable) chunk() handles n chunks fitting", () => { - assertEquals( - chunk([1, 2, 3, 4, 5, 6], 2), - [[1, 2], [3, 4], [5, 6]], - "size=2", - ); - assertEquals(chunk([1, 2, 3, 4, 5, 6], 3), [[1, 2, 3], [4, 5, 6]], "size=3"); -}); - -Deno.test("chunk() handles n chunks not fitting", () => { - assertEquals( - chunk([1, 2, 3, 4, 5, 6], 4), - [[1, 2, 3, 4], [5, 6]], - "size 4 with 6 elements should return 2 chunks with 4 and 2 elements", - ); - assertEquals( - chunk([1, 2, 3, 4, 5, 6], 5), - [[1, 2, 3, 4, 5], [6]], - "size 5 with 6 elements should return 2 chunks with 5 and 1 elements", - ); -}); - -Deno.test("(unstable) chunk() handles n chunks larger than input length", () => { - assertEquals( - chunk([1, 2, 3, 4, 5, 6], 10), - [[1, 2, 3, 4, 5, 6]], - ); -}); - -Deno.test({ - name: "(unstable) chunk() handles chunks equal to length", - fn() { - assertEquals( - chunk([1, 2, 3, 4, 5, 6], 6), - [[1, 2, 3, 4, 5, 6]], - ); - }, -}); - -Deno.test("(unstable) chunk() handles a generator", () => { - function* gen() { - yield "a"; - yield "b"; - yield "c"; - yield "d"; - } - assertEquals(chunk(gen(), 1), [["a"], ["b"], ["c"], ["d"]], "size = 1"); - assertEquals(chunk(gen(), 2), [["a", "b"], ["c", "d"]], "size = 2"); - assertEquals(chunk(gen(), 3), [["a", "b", "c"], ["d"]], "size = 3"); - assertEquals(chunk(gen(), 4), [["a", "b", "c", "d"]], "size = gen.length"); - assertEquals(chunk(gen(), 5), [["a", "b", "c", "d"]], "size > gen.length"); -}); - -Deno.test("(unstable) chunk() handles a string", () => { - assertEquals(chunk("abcdefg", 4), [ - ["a", "b", "c", "d"], - ["e", "f", "g"], - ]); -}); - -Deno.test("(unstable) chunk() handles a Set", () => { - const set = new Set([1, 2, 3, 4, 5, 6]); - assertEquals(chunk(set, 2), [ - [1, 2], - [3, 4], - [5, 6], - ]); -}); - -Deno.test("(unstable) chunk() handles a Map", () => { - const map = new Map([ - ["a", 1], - ["b", 2], - ["c", 3], - ["d", 4], - ["e", 5], - ["f", 6], - ]); - assertEquals(chunk(map, 2), [ - [ - ["a", 1], - ["b", 2], - ], - [ - ["c", 3], - ["d", 4], - ], - [ - ["e", 5], - ["f", 6], - ], - ]); -}); - -Deno.test("(unstable) chunk() handles user-defined iterable", () => { - class MyIterable { - *[Symbol.iterator]() { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - yield 6; - } - } - assertEquals(chunk(new MyIterable(), 2), [ - [1, 2], - [3, 4], - [5, 6], - ]); -}); - -Deno.test("(unstable) chunk() handles a TypedArrays", () => { - const typedArrays = [ - Uint8Array, - Uint8ClampedArray, - Uint16Array, - Uint32Array, - Int8Array, - Int16Array, - Int32Array, - Float32Array, - Float64Array, - ]; - for (const TypedArray of typedArrays) { - const array = new TypedArray([1, 2, 3, 4, 5, 6]); - assertEquals(chunk(array, 2), [ - [1, 2], - [3, 4], - [5, 6], - ]); - } -}); - -Deno.test("(unstable) chunk() handles an array with empty slots", () => { - // Regression test for chunk that only allowed Array instances instead of any Iterable. - // This is a special case where an array is filled with empty slots which is a different kind of nothing than null or undefined - // Typed arrays are not affected, as they are filled with 0 instead of empty slots - const arr = new Array(4); - arr[2] = 3; - - const expectedSecondChunk = new Array(2); - expectedSecondChunk[0] = 3; - assertEquals(chunk(arr, 2), [ - new Array(2), - expectedSecondChunk, - ]); -}); diff --git a/collections/unstable_drop_last_while.ts b/collections/unstable_drop_last_while.ts deleted file mode 100644 index 2636f08b8fae..000000000000 --- a/collections/unstable_drop_last_while.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns an array that drops all elements in the given iterable until the - * last element that does not match the given predicate. - * - * @typeParam T The type of the elements in the input array. - * - * @param iterable The iterable to drop elements from. - * @param predicate The function to test each element for a condition. - * - * @returns An array that drops all elements until the last element that does - * not match the given predicate. - * - * @example Basic usage - * ```ts - * import { dropLastWhile } from "@std/collections/unstable-drop-last-while"; - * import { assertEquals } from "@std/assert"; - * - * const numbers = [11, 42, 55, 20, 33, 44]; - * - * const notFortyFour = dropLastWhile(numbers, (number) => number > 30); - * - * assertEquals(notFortyFour, [11, 42, 55, 20]); - * ``` - */ -export function dropLastWhile( - iterable: Iterable, - predicate: (el: T) => boolean, -): T[] { - const array = Array.isArray(iterable) ? iterable : Array.from(iterable); - let offset = array.length - 1; - while (offset >= 0 && predicate(array[offset]!)) { - offset--; - } - return array.slice(0, offset + 1); -} diff --git a/collections/unstable_drop_last_while_test.ts b/collections/unstable_drop_last_while_test.ts deleted file mode 100644 index 48305fd0ce09..000000000000 --- a/collections/unstable_drop_last_while_test.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -import { dropLastWhile } from "./unstable_drop_last_while.ts"; -import { assertEquals } from "@std/assert"; - -Deno.test("(unstable) dropLastWhile() handles num array", () => { - const values = [20, 33, 44]; - const actual = dropLastWhile(values, (i) => i > 30); - assertEquals(actual, [20]); -}); - -Deno.test("(unstable) dropLastWhile() does not mutate the input array", () => { - const array = [1, 2, 3, 4, 5, 6]; - const actual = dropLastWhile(array, (i) => i > 4); - assertEquals(actual, [1, 2, 3, 4]); - assertEquals(array, [1, 2, 3, 4, 5, 6]); -}); - -Deno.test("(unstable) dropLastWhile() handles negatives", () => { - const array = [-1, -2, -3, -4, -5, -6]; - const actual = dropLastWhile(array, (i) => i < -4); - assertEquals(actual, [-1, -2, -3, -4]); -}); - -Deno.test("(unstable) dropLastWhile() handles empty input returns empty array", () => { - const array: number[] = []; - const actual = dropLastWhile(array, (i) => i > 4); - assertEquals(actual, []); -}); - -Deno.test("(unstable) dropLastWhile() returns same array when the last element doesn't get dropped", () => { - const array = [40, 30, 20]; - const actual = dropLastWhile(array, (i) => i > 40); - assertEquals(actual, [40, 30, 20]); -}); - -Deno.test("(unstable) dropLastWhile() returns empty array when all elements get dropped", () => { - const array = [20, 30, 20]; - const actual = dropLastWhile(array, (i) => i < 40); - assertEquals(actual, []); -}); - -Deno.test("(unstable) dropLastWhile() handles a string", () => { - const values = "hello there world"; - const actual = dropLastWhile(values, (i) => i !== " "); - assertEquals(actual, "hello there ".split("")); -}); - -Deno.test("(unstable) dropLastWhile() handles a Set", () => { - const values = new Set([20, 33, 44]); - const actual = dropLastWhile(values, (i) => i > 30); - assertEquals(actual, [20]); -}); - -Deno.test("(unstable) dropLastWhile() handles a Map", () => { - const values = new Map([ - ["a", 20], - ["b", 33], - ["c", 44], - ]); - const actual = dropLastWhile(values, ([_k, v]) => v > 30); - assertEquals(actual, [["a", 20]]); -}); - -Deno.test("(unstable) dropLastWhile() handles a generator", () => { - function* gen() { - yield 20; - yield 33; - yield 44; - } - const actual = dropLastWhile(gen(), (i) => i > 30); - assertEquals(actual, [20]); -}); diff --git a/collections/unstable_drop_while.ts b/collections/unstable_drop_while.ts deleted file mode 100644 index 912adf95d224..000000000000 --- a/collections/unstable_drop_while.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns an array that drops all elements in the given iterable until the - * first element that does not match the given predicate. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the input array. - * - * @param iterable The iterable to drop elements from. - * @param predicate The function to test each element for a condition. - * - * @returns An array that drops all elements until the first element that - * does not match the given predicate. - * - * @example Basic usage - * ```ts - * import { dropWhile } from "@std/collections/unstable-drop-while"; - * import { assertEquals } from "@std/assert"; - * - * const numbers = [3, 2, 5, 2, 5]; - * const dropWhileNumbers = dropWhile(numbers, (number) => number !== 2); - * - * assertEquals(dropWhileNumbers, [2, 5, 2, 5]); - * ``` - */ -export function dropWhile( - iterable: Iterable, - predicate: (el: T) => boolean, -): T[] { - if (Array.isArray(iterable)) { - const idx = iterable.findIndex((el) => !predicate(el)); - if (idx === -1) { - return []; - } - return iterable.slice(idx); - } - const array: T[] = []; - let found = false; - for (const item of iterable) { - if (found || !predicate(item)) { - found = true; - array.push(item); - } - } - return array; -} diff --git a/collections/unstable_drop_while_test.ts b/collections/unstable_drop_while_test.ts deleted file mode 100644 index 6d3fead06e52..000000000000 --- a/collections/unstable_drop_while_test.ts +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assertEquals } from "@std/assert"; -import { dropWhile } from "./unstable_drop_while.ts"; - -Deno.test("(unstable) dropWhile matches first element", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = dropWhile(arr, (i) => i !== 1); - - assertEquals(actual, [1, 2, 3, 4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() handles Array", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = dropWhile(arr, (i) => i !== 2); - - assertEquals(actual, [2, 3, 4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() adds two to each num in predicate", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = dropWhile(arr, (i) => i + 2 !== 6); - - assertEquals(actual, [4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() handles negatives", () => { - const arr = [-5, -6]; - - const actual = dropWhile(arr, (i) => i < -4); - assertEquals(actual, []); -}); - -Deno.test("(unstable) dropWhile() handles no mutation", () => { - const arr = [1, 2, 3, 4, 5, 6]; - - const actual = dropWhile(arr, (i) => i !== 4); - assertEquals(actual, [4, 5, 6]); - assertEquals(arr, [1, 2, 3, 4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() handles empty input array returns empty array", () => { - const arr: number[] = []; - - const actual = dropWhile(arr, (i) => i > 4); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) dropWhile() returns empty array when the last element doesn't match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = dropWhile(arr, (i) => i !== 4); - - assertEquals(actual, [4]); -}); - -Deno.test("(unstable) dropWhile() returns the same array when all elements match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = dropWhile(arr, (i) => i !== 400); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) dropWhile() handles a generator", () => { - function* gen() { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - yield 6; - } - - const actual = dropWhile(gen(), (i) => i !== 4); - assertEquals(actual, [4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() handles a Set", () => { - const set = new Set([1, 2, 3, 4, 5, 6]); - const actual = dropWhile(set, (i) => i !== 4); - assertEquals(actual, [4, 5, 6]); -}); - -Deno.test("(unstable) dropWhile() handles a Map", () => { - const map = new Map([ - ["a", 1], - ["b", 2], - ["c", 3], - ["d", 4], - ["e", 5], - ["f", 6], - ]); - - const actual = dropWhile(map, ([_k, v]) => v !== 4); - assertEquals(actual, [ - ["d", 4], - ["e", 5], - ["f", 6], - ]); -}); diff --git a/collections/unstable_intersect.ts b/collections/unstable_intersect.ts deleted file mode 100644 index 0aed7c4d6cd2..000000000000 --- a/collections/unstable_intersect.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns all distinct elements that appear at least once in each of the given - * iterables. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the input iterables. - * - * @param iterables The iterables to intersect. - * - * @returns An array of distinct elements that appear at least once in each of - * the given iterables. - * - * @example Basic usage - * ```ts - * import { intersect } from "@std/collections/unstable-intersect"; - * import { assertEquals } from "@std/assert"; - * - * const lisaInterests = ["Cooking", "Music", "Hiking"]; - * const kimInterests = ["Music", "Tennis", "Cooking"]; - * const commonInterests = intersect(lisaInterests, kimInterests); - * - * assertEquals(commonInterests, ["Cooking", "Music"]); - * ``` - */ -export function intersect(...iterables: Iterable[]): T[] { - const [iterable, ...otherIterables] = iterables; - let set = new Set(iterable); - if (set.size === 0) return []; - for (const iterable of otherIterables) { - set = set.intersection(new Set(iterable)); - if (set.size === 0) return []; - } - return [...set]; -} diff --git a/collections/unstable_intersect_test.ts b/collections/unstable_intersect_test.ts deleted file mode 100644 index aac7d7c26648..000000000000 --- a/collections/unstable_intersect_test.ts +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assert, assertEquals } from "@std/assert"; -import { intersect } from "./unstable_intersect.ts"; - -Deno.test("(unstable) intersect() handles no mutation", () => { - const arrayA = [1, 2, 3]; - const arrayB = [3, 4, 5]; - intersect(arrayA, arrayB); - assertEquals(arrayA, [1, 2, 3]); - assertEquals(arrayB, [3, 4, 5]); -}); - -Deno.test("(unstable) intersect() handles empty input", () => { - const actual = intersect(); - assertEquals(actual, []); -}); - -Deno.test("(unstable) intersect() handles empty arrays", () => { - const actual = intersect([], []); - assertEquals(actual, []); -}); - -Deno.test("(unstable) intersect() handles one side empty", () => { - const firstEmpty = intersect([], [1, 2, 3]); - const secondEmpty = intersect([1, 2, 3], []); - assertEquals(firstEmpty, []); - assertEquals(secondEmpty, []); -}); - -Deno.test("(unstable) intersect() handles empty result", () => { - const actual = intersect(["a", "b", "c"], ["d", "e", "f"]); - assertEquals(actual, []); -}); - -Deno.test("(unstable) intersect() handles one or more items in intersection", () => { - const one = intersect(["a", "b"], ["b", "c"]); - const orMore = intersect(["a", "b", "c", "d"], ["c", "d", "e", "f"]); - assertEquals(one, ["b"]); - assertEquals(orMore, ["c", "d"]); -}); - -Deno.test("(unstable) intersect() handles duplicates", () => { - const duplicates = intersect(["a", "b", "c", "b"], ["b", "c"]); - const moreDuplicates = intersect(["a", "b"], ["b", "b", "c", "c"]); - assertEquals(duplicates, ["b", "c"]); - assertEquals(moreDuplicates, ["b"]); -}); - -Deno.test("(unstable) intersect() handles more than two inputs", () => { - assertEquals( - intersect( - ["a", "b"], - ["b", "c"], - ["s", "b"], - ["b", "b"], - ), - ["b"], - ); - assertEquals( - intersect( - [1], - [1], - [2], - ), - [], - ); - - assertEquals( - intersect( - [true, false], - [true, false], - [true], - ), - [true], - ); -}); - -Deno.test("(unstable) intersect() handles objects", () => { - assertEquals( - intersect>( - [{ foo: "bar" }, { bar: "baz" }], - [{ fruit: "banana" }], - ), - [], - ); - - const obj = { bar: "baz" }; - assertEquals( - intersect>( - [{ foo: "bar" }, obj], - [obj], - ), - [obj], - ); - - assertEquals( - intersect>( - [{ foo: "bar" }, { bar: "baz" }], - [{ bar: "banana" }], - ), - [], - ); -}); - -Deno.test("(unstable) intersect() handles functions", () => { - assertEquals( - intersect( - [() => {}, () => null], - [() => NaN], - ), - [], - ); - - const emptyObjectFunction = () => {}; - assertEquals( - intersect( - [emptyObjectFunction, () => null], - [emptyObjectFunction], - ), - [emptyObjectFunction], - ); - assertEquals( - intersect( - [(a: number, b: number) => a + b, () => null], - [(a: number, b: number) => a - b], - ), - [], - ); -}); - -// If you are using sets using {@linkcode Set.prototype.intersection} directly is more efficient. -Deno.test("(unstable) intersect() handles sets", () => { - const a = new Set([1, 2, 3, 4]); - const b = new Set([2, 3]); - assertEquals(intersect(a, b), [2, 3]); -}); - -Deno.test("(unstable) intersect() handles iterables of different types", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const set = new Set([1, 2, 3, 4, 5]); - function* gen() { - yield 1; - yield 2; - yield 3; - } - const iterable = { - *[Symbol.iterator]() { - yield 3; - yield 6; - }, - }; - assertEquals(intersect(arr, set, gen(), iterable), [3]); -}); - -Deno.test("(unstable) intersect() handles iterables with no mutation", () => { - const a = [1, 2, 3, 4]; - const b = new Set([2, 3]); - intersect(a, b); - assertEquals(a, [1, 2, 3, 4]); - assert(b.has(2)); - assert(b.has(3)); -}); - -Deno.test("(unstable) intersect() handles generators", () => { - function* gen() { - yield 1; - yield 2; - yield 3; - yield 4; - } - function* gen2() { - yield 2; - yield 3; - } - assertEquals(intersect(gen(), gen2()), [2, 3]); -}); diff --git a/collections/unstable_sample.ts b/collections/unstable_sample.ts deleted file mode 100644 index f45f478be87c..000000000000 --- a/collections/unstable_sample.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns a random element from the given iterable. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the iterable. - * - * @param iterable The iterable to sample from. - * - * @returns A random element from the given iterable, or `undefined` if the iterable has no elements. - * - * @example Basic usage - * ```ts - * import { sample } from "@std/collections/unstable-sample"; - * import { assertArrayIncludes } from "@std/assert"; - * - * const numbers = [1, 2, 3, 4]; - * const random = sample(numbers); - * - * assertArrayIncludes(numbers, [random]); - * ``` - */ -export function sample(iterable: Iterable): T | undefined { - let array: readonly T[]; - if (Array.isArray(iterable)) { - array = iterable; - } else { - array = Array.from(iterable); - } - const length: number = array.length; - if (length === 0) { - return undefined; - } - const randomIndex = Math.floor(Math.random() * length); - return array[randomIndex]; -} diff --git a/collections/unstable_sample_test.ts b/collections/unstable_sample_test.ts deleted file mode 100644 index 5783d4ffccf0..000000000000 --- a/collections/unstable_sample_test.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assert, assertEquals } from "@std/assert"; -import { sample } from "./unstable_sample.ts"; - -Deno.test("(unstable) sample() does not mutate input", () => { - const array = ["a", "abc", "ba"]; - sample(array); - assertEquals(array, ["a", "abc", "ba"]); -}); - -Deno.test("(unstable) sample() handles empty input", () => { - const actual = sample([]); - assertEquals(actual, undefined); -}); - -Deno.test("(unstable) sample() handles array of numbers", () => { - const input = [1, 2, 3]; - const actual = sample([1, 2, 3]); - assert(actual && input.includes(actual)); -}); - -Deno.test("(unstable) sample() handles array of objects", () => { - const input = [ - { - name: "(unstable) Anna", - age: 18, - }, - { - name: "(unstable) Kim", - age: 24, - }, - ]; - const actual = sample(input); - assert(actual && input.includes(actual)); -}); - -Deno.test("(unstable) sample() returns the first element if there is only one element", () => { - const input = [1]; - const actual = sample(input); - assertEquals(actual, 1); -}); - -Deno.test("(unstable) sample() handles a string", () => { - const input = "abc"; - const actual = sample(input); - assert(actual && input.includes(actual)); -}); - -Deno.test("(unstable) sample() handles a generator", () => { - function* gen() { - yield 1; - yield 2; - yield 3; - } - const actual = sample(gen()); - assert(actual !== undefined); - assert(Array.from(gen()).includes(actual)); -}); - -Deno.test("(unstable) sample() handles an array with empty slots", () => { - const input = Array(10); - const actual = sample(input); - assertEquals(actual, undefined); -}); - -Deno.test("(unstable) sample() handles a Set", () => { - const input = new Set([1, 2, 3]); - const actual = sample(input); - assert(actual !== undefined); - assert(input.has(actual)); -}); - -Deno.test("(unstable) sample() handles a Map", () => { - const input = new Map([ - ["a", 1], - ["b", 2], - ["c", 3], - ]); - const actual = sample(input); - assert(actual !== undefined); - assert(Array.isArray(actual)); - assert(actual.length === 2); - assert(input.has(actual[0])); - assert(input.get(actual[0]) === actual[1]); -}); - -Deno.test("(unstable) sample() handles TypedArrays", () => { - const typedArrays = [ - Uint8Array, - Uint8ClampedArray, - Uint16Array, - Uint32Array, - Int8Array, - Int16Array, - Int32Array, - Float32Array, - Float64Array, - ]; - for (const TypedArray of typedArrays) { - const input = new TypedArray([1, 2, 3]); - const actual = sample(input); - assert(actual !== undefined); - assert(input.includes(actual)); - } -}); - -Deno.test("(unstable) sample() handles a string", () => { - const input = "abc"; - const actual = sample("abc"); - assert(actual !== undefined); - assert(actual.length === 1); - assert(input.includes(actual)); -}); - -Deno.test("(unstable) sample() handles user-defined iterable", () => { - const iterable = { - *[Symbol.iterator]() { - yield "a"; - yield "b"; - yield "c"; - }, - }; - const actual = sample(iterable); - assert(actual !== undefined); - assert(["a", "b", "c"].includes(actual)); -}); diff --git a/collections/unstable_sliding_windows.ts b/collections/unstable_sliding_windows.ts deleted file mode 100644 index 0cee05031138..000000000000 --- a/collections/unstable_sliding_windows.ts +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018-2025 the Deno authors. 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( - iterable: Iterable, - 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; -} diff --git a/collections/unstable_sliding_windows_test.ts b/collections/unstable_sliding_windows_test.ts deleted file mode 100644 index 941f5e1c98d4..000000000000 --- a/collections/unstable_sliding_windows_test.ts +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assertEquals, assertThrows } from "@std/assert"; -import { slidingWindows } from "./unstable_sliding_windows.ts"; - -function slidingWindowsTest( - input: Parameters, - 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, - "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]], - ]); -}); diff --git a/collections/unstable_sort_by.ts b/collections/unstable_sort_by.ts deleted file mode 100644 index 4dcf6312b4e1..000000000000 --- a/collections/unstable_sort_by.ts +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** Order option for {@linkcode SortByOptions}. */ -export type Order = "asc" | "desc"; - -/** Options for {@linkcode sortBy}. */ -export type SortByOptions = { - /** - * The order to sort the elements in. - * - * @default {"asc"} - */ - order: Order; -}; - -/** - * Types that can be compared with other values of the same type - * using comparison operators. - */ -export type Comparable = number | string | bigint | Date; - -/** - * Returns all elements in the given collection, sorted by their result using - * the given selector. The selector function is called only once for each - * element. Ascending or descending order can be specified through the `order` - * option. By default, the elements are sorted in ascending order. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the iterator elements. - * @typeParam U The type of the selected values. - * - * @param iterator The iterator to sort. - * @param selector The selector function to get the value to sort by. - * @param options The options for sorting. - * - * @returns A new array containing all elements sorted by the selector. - * - * @example Usage with numbers - * ```ts - * import { sortBy } from "@std/collections/sort-by"; - * import { assertEquals } from "@std/assert"; - * - * const people = [ - * { name: "Anna", age: 34 }, - * { name: "Kim", age: 42 }, - * { name: "John", age: 23 }, - * ]; - * const sortedByAge = sortBy(people, (person) => person.age); - * - * assertEquals(sortedByAge, [ - * { name: "John", age: 23 }, - * { name: "Anna", age: 34 }, - * { name: "Kim", age: 42 }, - * ]); - * - * const sortedByAgeDesc = sortBy(people, (person) => person.age, { order: "desc" }); - * - * assertEquals(sortedByAgeDesc, [ - * { name: "Kim", age: 42 }, - * { name: "Anna", age: 34 }, - * { name: "John", age: 23 }, - * ]); - * ``` - * - * @example Usage with strings - * ```ts - * import { sortBy } from "@std/collections/sort-by"; - * import { assertEquals } from "@std/assert"; - * - * const people = [ - * { name: "Anna" }, - * { name: "Kim" }, - * { name: "John" }, - * ]; - * const sortedByName = sortBy(people, (it) => it.name); - * - * assertEquals(sortedByName, [ - * { name: "Anna" }, - * { name: "John" }, - * { name: "Kim" }, - * ]); - * ``` - * - * @example Usage with bigints - * ```ts - * import { sortBy } from "@std/collections/sort-by"; - * import { assertEquals } from "@std/assert"; - * - * const people = [ - * { name: "Anna", age: 34n }, - * { name: "Kim", age: 42n }, - * { name: "John", age: 23n }, - * ]; - * - * const sortedByAge = sortBy(people, (person) => person.age); - * - * assertEquals(sortedByAge, [ - * { name: "John", age: 23n }, - * { name: "Anna", age: 34n }, - * { name: "Kim", age: 42n }, - * ]); - * ``` - * - * @example Usage with Date objects - * ```ts - * import { sortBy } from "@std/collections/sort-by"; - * import { assertEquals } from "@std/assert"; - * - * const people = [ - * { name: "Anna", startedAt: new Date("2020-01-01") }, - * { name: "Kim", startedAt: new Date("2020-03-01") }, - * { name: "John", startedAt: new Date("2020-06-01") }, - * ]; - * - * const sortedByStartedAt = sortBy(people, (people) => people.startedAt); - * - * assertEquals(sortedByStartedAt, [ - * { name: "Anna", startedAt: new Date("2020-01-01") }, - * { name: "Kim", startedAt: new Date("2020-03-01") }, - * { name: "John", startedAt: new Date("2020-06-01") }, - * ]); - * ``` - */ -export function sortBy( - iterator: Iterable, - selector: (el: T) => U, - options?: SortByOptions, -): T[] { - const array: { value: T; selected: U }[] = []; - - for (const item of iterator) { - array.push({ value: item, selected: selector(item) }); - } - - array.sort((oa, ob) => { - const a = oa.selected; - const b = ob.selected; - const order = options?.order === "desc" ? -1 : 1; - - if (Number.isNaN(a)) return order; - if (Number.isNaN(b)) return -order; - - return order * (a > b ? 1 : a < b ? -1 : 0); - }); - - return array.map((item) => item.value); -} diff --git a/collections/unstable_take_last_while.ts b/collections/unstable_take_last_while.ts deleted file mode 100644 index d373595a3de7..000000000000 --- a/collections/unstable_take_last_while.ts +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns all elements in the given iterable after the last element that does not - * match the given predicate. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the iterable elements. - * - * @param iterable The iterable to take elements from. - * @param predicate The predicate function to determine if an element should be - * included. - * - * @returns An array containing all elements after the last element that does - * not match the predicate. - * - * @example Basic usage - * ```ts - * import { takeLastWhile } from "@std/collections/unstable-take-last-while"; - * import { assertEquals } from "@std/assert"; - * - * const numbers = [1, 2, 3, 4, 5, 6]; - * const result = takeLastWhile(numbers, (number) => number > 4); - * assertEquals(result, [5, 6]); - * ``` - */ -export function takeLastWhile( - iterable: Iterable, - predicate: (el: T) => boolean, -): T[] { - if (Array.isArray(iterable)) { - let offset = iterable.length; - while (0 < offset && predicate(iterable[offset - 1] as T)) { - offset--; - } - return iterable.slice(offset); - } - const result: T[] = []; - for (const el of iterable) { - if (predicate(el)) { - result.push(el); - } else { - result.length = 0; - } - } - return result; -} diff --git a/collections/unstable_take_last_while_test.ts b/collections/unstable_take_last_while_test.ts deleted file mode 100644 index c91c9d9a5dc3..000000000000 --- a/collections/unstable_take_last_while_test.ts +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assertEquals } from "@std/assert"; -import { takeLastWhile } from "./unstable_take_last_while.ts"; - -Deno.test("(unstable) takeLastWhile() handles num array", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = takeLastWhile(arr, (i) => i !== 4); - - assertEquals(actual, [5, 6]); -}); - -Deno.test("(unstable) takeLastWhile() adds two to each num in predicate", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = takeLastWhile(arr, (i) => i + 2 !== 6); - - assertEquals(actual, [5, 6]); -}); - -Deno.test("(unstable) takeLastWhile() handles negatives", () => { - const arr = [-1, -2, -3, -4, -5, -6]; - - const actual = takeLastWhile(arr, (i) => i < -4); - assertEquals(actual, [-5, -6]); -}); - -Deno.test("(unstable) takeLastWhile() handles no mutation", () => { - const arr = [1, 2, 3, 4, 5, 6]; - - const actual = takeLastWhile(arr, (i) => i !== 4); - assertEquals(actual, [5, 6]); - assertEquals(arr, [1, 2, 3, 4, 5, 6]); -}); - -Deno.test("(unstable) takeLastWhile() handles empty input array returns empty array", () => { - const arr: number[] = []; - - const actual = takeLastWhile(arr, (i) => i > 4); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeLastWhile() returns empty array when the last element doesn't match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = takeLastWhile(arr, (i) => i !== 4); - - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeLastWhile() returns the same array when all elements match the predicate", () => { - const arr = [1, 2, 3, 4]; - - const actual = takeLastWhile(arr, (i) => i !== 400); - - assertEquals(actual, [1, 2, 3, 4]); -}); - -Deno.test("(unstable) handles generator", () => { - function* gen() { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - yield 6; - } - const actual = takeLastWhile(gen(), (i) => i !== 4); - assertEquals(actual, [5, 6]); -}); - -Deno.test("(unstable) takeLastWhile() returns empty array when the last generator element does not match the predicate", () => { - function* gen() { - yield 1; - yield 2; - yield 3; - yield 4; - } - - const actual = takeLastWhile(gen(), (i) => i !== 4); - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeLastWhile() returns the same array when all elements match the predicate", () => { - function* gen(): Generator { - yield 1; - yield 2; - yield 3; - yield 4; - } - const actual = takeLastWhile(gen(), (i) => i !== 400); - assertEquals(actual, [1, 2, 3, 4]); -}); - -Deno.test("(unstable) takeLastWhile() empty generator returns empty array", () => { - function* gen(): Generator {} - const actual = takeLastWhile(gen(), (i) => i > 4); - assertEquals(actual, []); -}); - -Deno.test("(unstable) takeLastWhile() gets from last matching element from an array", () => { - const arr = [1, 2, 3, 4, 5, 6]; - const actual = takeLastWhile(arr, (i) => i !== 2 && i !== 4); - assertEquals(actual, [5, 6]); -}); - -Deno.test("(unstable) takeLastWhile() gets from last matching element from a generator", () => { - function* gen(): Generator { - yield 1; - yield 2; - yield 3; - yield 4; - yield 5; - yield 6; - } - const actual = takeLastWhile(gen(), (i) => i !== 2 && i !== 4); - assertEquals(actual, [5, 6]); -}); diff --git a/collections/unstable_take_while.ts b/collections/unstable_take_while.ts deleted file mode 100644 index f7817e49bbc6..000000000000 --- a/collections/unstable_take_while.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns all elements in the given collection until the first element that - * does not match the given predicate. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the iterable. - * - * @param iterable The iterable to take elements from. - * @param predicate The predicate function to determine if an element should be - * included. - * - * @returns An array containing all elements until the first element that - * does not match the predicate. - * - * @example Basic usage - * ```ts - * import { takeWhile } from "@std/collections/take-while"; - * import { assertEquals } from "@std/assert"; - * - * const numbers = [1, 2, 3, 4, 5, 6]; - * - * const result = takeWhile(numbers, (number) => number < 4); - * - * assertEquals(result, [1, 2, 3]); - * ``` - */ -export function takeWhile( - iterable: Iterable, - predicate: (el: T) => boolean, -): T[] { - const result: T[] = []; - for (const element of iterable) { - if (!predicate(element)) { - break; - } - result.push(element); - } - return result; -} diff --git a/collections/unstable_without_all.ts b/collections/unstable_without_all.ts deleted file mode 100644 index 20b0dd3b02c4..000000000000 --- a/collections/unstable_without_all.ts +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. -// This module is browser compatible. - -/** - * Returns an array excluding all given values from an iterable. - * - * @experimental **UNSTABLE**: New API, yet to be vetted. - * - * @typeParam T The type of the elements in the iterable. - * - * @param iterable The iterable to exclude values from. - * @param values The values to exclude from the iterable. - * - * @returns An array containing all elements from iterables except the - * ones that are in the values iterable. - * - * @remarks - * If both inputs are a {@linkcode Set}, and you want the difference as a - * {@linkcode Set}, you could use {@linkcode Set.prototype.difference} instead. - * - * @example Basic usage - * ```ts - * import { withoutAll } from "@std/collections/unstable-without-all"; - * import { assertEquals } from "@std/assert"; - * - * const withoutList = withoutAll([2, 1, 2, 3], [1, 2]); - * - * assertEquals(withoutList, [3]); - * ``` - */ -export function withoutAll(iterable: Iterable, values: Iterable): T[] { - const excludedSet = new Set(values); - const result: T[] = []; - for (const value of iterable) { - if (excludedSet.has(value)) { - continue; - } - result.push(value); - } - return result; -} diff --git a/collections/unstable_without_all_test.ts b/collections/unstable_without_all_test.ts deleted file mode 100644 index c8e39fe82946..000000000000 --- a/collections/unstable_without_all_test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2018-2025 the Deno authors. MIT license. - -import { assertEquals } from "@std/assert"; -import { withoutAll } from "./unstable_without_all.ts"; - -function withoutAllTest( - input: Array, - excluded: Array, - expected: Array, - message?: string, -) { - const actual = withoutAll(input, excluded); - assertEquals(actual, expected, message); -} - -Deno.test({ - name: "(unstable) withoutAll() handles no mutation", - fn() { - const array = [1, 2, 3, 4]; - withoutAll(array, [2, 3]); - assertEquals(array, [1, 2, 3, 4]); - }, -}); - -Deno.test({ - name: "(unstable) withoutAll() handles empty input", - fn() { - withoutAllTest([], [], []); - }, -}); - -Deno.test({ - name: "(unstable) withoutAll() handles no matches", - fn() { - withoutAllTest([1, 2, 3, 4], [0, 7, 9], [1, 2, 3, 4]); - }, -}); - -Deno.test({ - name: "(unstable) withoutAll() handles single match", - fn() { - withoutAllTest([1, 2, 3, 4], [1], [2, 3, 4]); - withoutAllTest([1, 2, 3, 2], [2], [1, 3]); - }, -}); - -Deno.test({ - name: "(unstable) withoutAll() handles multiple matches", - fn() { - withoutAllTest([1, 2, 3, 4, 6, 3], [1, 2], [3, 4, 6, 3]); - withoutAllTest([7, 2, 9, 8, 7, 6, 5, 7], [7, 9], [2, 8, 6, 5]); - }, -}); - -Deno.test({ - name: "(unstable) withoutAll() leaves duplicate elements", - fn() { - withoutAllTest( - Array.from({ length: 110 }, () => 3), - [1], - Array.from({ length: 110 }, () => 3), - ); - }, -}); - -Deno.test("(unstable) withoutAll() handles generators", () => { - function* genInput() { - yield 1; - yield 2; - yield 3; - yield 4; - } - function* genExcluded() { - yield 2; - yield 3; - } - const result = withoutAll(genInput(), genExcluded()); - assertEquals(result, [1, 4]); -}); - -Deno.test("(unstable) withoutAll() handles iterators", () => { - const input = new Set([1, 2, 3, 4]); - const excluded = new Set([2, 3]); - const result = withoutAll(input.values(), excluded.values()); - assertEquals(result, [1, 4]); -}); - -Deno.test("(unstable) withoutAll() handles a mix of inputs", () => { - const a = [1, 2, 3, 4]; - const b = new Set([2, 3, 5]); - assertEquals(withoutAll(a, b), [1, 4], "Array and Set"); - assertEquals(withoutAll(b, a), [5], "Set and Array"); -}); - -Deno.test("(unstable) withoutAll() handles allows excluded to be a superset of types", () => { - const a = [1, 2, 3, 4]; - const b = [1, "other", 3, 4]; - assertEquals(withoutAll(a, b), [2]); -}); - -Deno.test("(unstable) withoutAll() works with sets", () => { - const a = new Set([1, 2, 3, 4]); - const b = new Set([2, 3]); - assertEquals(withoutAll(a, b), [1, 4]); -}); diff --git a/collections/without_all.ts b/collections/without_all.ts index 715d2bd6bdd0..f980fa3eaf6d 100644 --- a/collections/without_all.ts +++ b/collections/without_all.ts @@ -2,15 +2,18 @@ // This module is browser compatible. /** - * Returns an array excluding all given values. + * Returns an array excluding all given values from an iterable. * - * @typeParam T The type of the array elements. + * Note: If both inputs are {@linkcode Set}s, and you want the difference as a + * {@linkcode Set}, you could use {@linkcode Set.prototype.difference} instead. * - * @param array The array to exclude values from. - * @param values The values to exclude from the array. + * @typeParam T The type of the elements in the iterable. * - * @returns A new array containing all elements from the given array except the - * ones that are in the values array. + * @param iterable The iterable to exclude values from. + * @param values The values to exclude from the iterable. + * + * @returns An array containing all elements from iterables except the + * ones that are in the values iterable. * * @example Basic usage * ```ts @@ -22,7 +25,14 @@ * assertEquals(withoutList, [3]); * ``` */ -export function withoutAll(array: readonly T[], values: readonly T[]): T[] { - const toExclude = new Set(values); - return array.filter((it) => !toExclude.has(it)); +export function withoutAll(iterable: Iterable, values: Iterable): T[] { + const excludedSet = new Set(values); + const result: T[] = []; + for (const value of iterable) { + if (excludedSet.has(value)) { + continue; + } + result.push(value); + } + return result; } diff --git a/collections/without_all_test.ts b/collections/without_all_test.ts index 7e902488e0a1..0dd313a652cf 100644 --- a/collections/without_all_test.ts +++ b/collections/without_all_test.ts @@ -62,3 +62,44 @@ Deno.test({ ); }, }); + +Deno.test("withoutAll() handles generators", () => { + function* genInput() { + yield 1; + yield 2; + yield 3; + yield 4; + } + function* genExcluded() { + yield 2; + yield 3; + } + const result = withoutAll(genInput(), genExcluded()); + assertEquals(result, [1, 4]); +}); + +Deno.test("withoutAll() handles iterators", () => { + const input = new Set([1, 2, 3, 4]); + const excluded = new Set([2, 3]); + const result = withoutAll(input.values(), excluded.values()); + assertEquals(result, [1, 4]); +}); + +Deno.test("withoutAll() handles a mix of inputs", () => { + const a = [1, 2, 3, 4]; + const b = new Set([2, 3, 5]); + assertEquals(withoutAll(a, b), [1, 4], "Array and Set"); + assertEquals(withoutAll(b, a), [5], "Set and Array"); +}); + +Deno.test("withoutAll() handles allows excluded to be a superset of types", () => { + const a = [1, 2, 3, 4]; + const b = [1, "other", 3, 4]; + assertEquals(withoutAll(a, b), [2]); +}); + +Deno.test("withoutAll() works with sets", () => { + const a = new Set([1, 2, 3, 4]); + const b = new Set([2, 3]); + assertEquals(withoutAll(a, b), [1, 4]); +});