diff --git a/collections/deno.json b/collections/deno.json index 6b49b7b0d4fb..2290619d0762 100644 --- a/collections/deno.json +++ b/collections/deno.json @@ -48,6 +48,22 @@ "./union": "./union.ts", "./unstable-binary-search": "./unstable_binary_search.ts", "./unstable-cycle": "./unstable_cycle.ts", + "./unstable-distinct-by": "./unstable_distinct_by.ts", + "./unstable-drop-last-while": "./unstable_drop_last_while.ts", + "./unstable-drop-while": "./unstable_drop_while.ts", + "./unstable-find-single": "./unstable_find_single.ts", + "./unstable-first-not-nullish-of": "./unstable_first_not_nullish_of.ts", + "./unstable-join-to-string": "./unstable_join_to_string.ts", + "./unstable-map-not-nullish": "./unstable_map_not_nullish.ts", + "./unstable-max-by": "./unstable_max_by.ts", + "./unstable-max-of": "./unstable_max_of.ts", + "./unstable-min-by": "./unstable_min_by.ts", + "./unstable-min-of": "./unstable_min_of.ts", + "./unstable-partition": "./unstable_partition.ts", + "./unstable-sort-by": "./unstable_sort_by.ts", + "./unstable-sum-of": "./unstable_sum_of.ts", + "./unstable-take-last-while": "./unstable_take_last_while.ts", + "./unstable-take-while": "./unstable_take_while.ts", "./unzip": "./unzip.ts", "./without-all": "./without_all.ts", "./zip": "./zip.ts" diff --git a/collections/distinct_by_test.ts b/collections/distinct_by_test.ts index 064e6c7c30b5..d8a20ad28bf5 100644 --- a/collections/distinct_by_test.ts +++ b/collections/distinct_by_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { distinctBy } from "./distinct_by.ts"; +import * as unstable from "./unstable_distinct_by.ts"; function distinctByTest( array: Array, @@ -117,3 +118,14 @@ Deno.test({ ); }, }); + +Deno.test({ + name: "distinctBy() passes index to discriminator", + fn() { + const result = unstable.distinctBy( + [25, "asdf", true], + (_, index) => index > 1, + ); + assertEquals(result, [25, true]); + }, +}); diff --git a/collections/drop_last_while_test.ts b/collections/drop_last_while_test.ts index 21ce4972704a..729f20d21b09 100644 --- a/collections/drop_last_while_test.ts +++ b/collections/drop_last_while_test.ts @@ -1,6 +1,7 @@ // Copyright 2018-2026 the Deno authors. MIT license. import { dropLastWhile } from "./drop_last_while.ts"; import { assertEquals } from "@std/assert"; +import * as unstable from "./unstable_drop_last_while.ts"; Deno.test("dropLastWhile() handles num array", () => { const values = [20, 33, 44]; @@ -80,3 +81,11 @@ Deno.test("dropLastWhile() handles a generator", () => { const actual = dropLastWhile(gen(), (i) => i > 30); assertEquals(actual, [20]); }); + +Deno.test("unstable.dropLastWhile() passes index to predicate", () => { + const array = [20, 30, 20]; + + const actual = unstable.dropLastWhile(array, (_, index) => index > 1); + + assertEquals(actual, [20, 30]); +}); diff --git a/collections/drop_while_test.ts b/collections/drop_while_test.ts index 3bacb6d8056e..87157da401e4 100644 --- a/collections/drop_while_test.ts +++ b/collections/drop_while_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { dropWhile } from "./drop_while.ts"; +import * as unstable from "./unstable_drop_while.ts"; Deno.test("dropWhile() handles Array", () => { const arr = [1, 2, 3, 4, 5, 6]; @@ -107,3 +108,11 @@ Deno.test("dropWhile() handles a Map", () => { ["f", 6], ]); }); + +Deno.test("unstable.dropWhile() passes index to predicate", () => { + const array = [20, 30, 20]; + + const actual = unstable.dropWhile(array, (_, index) => index < 1); + + assertEquals(actual, [30, 20]); +}); diff --git a/collections/find_single_test.ts b/collections/find_single_test.ts index cffb4f98da28..d29c8f0d1b25 100644 --- a/collections/find_single_test.ts +++ b/collections/find_single_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { findSingle } from "./find_single.ts"; +import * as unstable from "./unstable_find_single.ts"; function findSingleTest( input: [Array, (element: I) => boolean], @@ -103,3 +104,14 @@ Deno.test({ ); }, }); + +Deno.test({ + name: "unstable.findSingle() passes index to predicate", + fn() { + const result = unstable.findSingle( + [9, 12, 13], + (_, index) => index === 1, + ); + assertEquals(result, 12); + }, +}); diff --git a/collections/first_not_nullish_of_test.ts b/collections/first_not_nullish_of_test.ts index 50650fb8a74f..4092d067b4e6 100644 --- a/collections/first_not_nullish_of_test.ts +++ b/collections/first_not_nullish_of_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { firstNotNullishOf } from "./first_not_nullish_of.ts"; +import * as unstable from "./unstable_first_not_nullish_of.ts"; function firstNotNullishOfTest( input: [Array, (el: T) => O | undefined | null], @@ -85,3 +86,14 @@ Deno.test({ ); }, }); + +Deno.test({ + name: "unstable.firstNotNullishOf() passes index to selector", + fn() { + const result = unstable.firstNotNullishOf( + [1, 2, 3, 4], + (it, index) => index < 1 ? null : it, + ); + assertEquals(result, 2); + }, +}); diff --git a/collections/join_to_string_test.ts b/collections/join_to_string_test.ts index 00eb82634a99..fa65d671a292 100644 --- a/collections/join_to_string_test.ts +++ b/collections/join_to_string_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { joinToString } from "./join_to_string.ts"; +import * as unstable from "./unstable_join_to_string.ts"; Deno.test({ name: "joinToString() handles no mutation", @@ -144,3 +145,14 @@ Deno.test({ assertEquals(out, "result: Kim and others are winners"); }, }); + +Deno.test({ + name: "unstable.joinToString() passes index to selector", + fn() { + const arr = ["Kim", "Anna", "Tim"]; + + const out = unstable.joinToString(arr, (it, index) => it + index); + + assertEquals(out, "Kim0,Anna1,Tim2"); + }, +}); diff --git a/collections/map_not_nullish_test.ts b/collections/map_not_nullish_test.ts index 6c25ec280d3b..7b3c0c4bf03a 100644 --- a/collections/map_not_nullish_test.ts +++ b/collections/map_not_nullish_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { mapNotNullish } from "./map_not_nullish.ts"; +import * as unstable from "./unstable_map_not_nullish.ts"; function mapNotNullishTest( input: [Array, (el: T) => O | undefined | null], @@ -90,3 +91,14 @@ Deno.test({ ); }, }); + +Deno.test({ + name: "unstable.mapNotNullish() passes index to transformer", + fn() { + const result = unstable.mapNotNullish( + [1, 2, 3, 4], + (it, index) => index === 1 ? null : it + index, + ); + assertEquals(result, [1, 5, 7]); + }, +}); diff --git a/collections/max_by_test.ts b/collections/max_by_test.ts index 73be52745c94..610c677a9e01 100644 --- a/collections/max_by_test.ts +++ b/collections/max_by_test.ts @@ -2,6 +2,7 @@ import { assertEquals, assertStrictEquals } from "@std/assert"; import { maxBy } from "./max_by.ts"; +import * as unstable from "./unstable_max_by.ts"; Deno.test("maxBy() handles array input", () => { const input = [ @@ -191,3 +192,14 @@ Deno.test("maxBy() handles Map values", () => { const max = maxBy(input.values(), (i) => i.age); assertEquals(max, { age: 45 }); }); + +Deno.test({ + name: "unstable.maxBy() passes index to selector", + fn() { + const input = [4, 3, 2, 1]; + + const max = unstable.maxBy(input, (_, index) => index); + + assertEquals(max, 1); + }, +}); diff --git a/collections/max_of_test.ts b/collections/max_of_test.ts index 78c0956c7b6e..03a75d9ac470 100644 --- a/collections/max_of_test.ts +++ b/collections/max_of_test.ts @@ -1,6 +1,7 @@ // Copyright 2018-2026 the Deno authors. MIT license. import { maxOf } from "./max_of.ts"; import { assertEquals } from "@std/assert"; +import * as unstable from "./unstable_max_of.ts"; Deno.test("maxOf() handles regular max", () => { const array = [5, 18, 35, 120]; @@ -142,3 +143,14 @@ Deno.test("maxOf() handles infinity", () => { assertEquals(actual, Infinity); }); + +Deno.test({ + name: "unstable.maxOf() passes index to selector", + fn() { + const input = [4, 3, 2, 1]; + + const max = unstable.maxOf(input, (it, index) => it * index); + + assertEquals(max, 4); + }, +}); diff --git a/collections/min_by_test.ts b/collections/min_by_test.ts index dd56d2442c69..ef707aa05d71 100644 --- a/collections/min_by_test.ts +++ b/collections/min_by_test.ts @@ -2,6 +2,7 @@ import { assertEquals, assertStrictEquals } from "@std/assert"; import { minBy } from "./min_by.ts"; +import * as unstable from "./unstable_min_by.ts"; Deno.test("minBy() handles array input", () => { const input = [ @@ -192,3 +193,14 @@ Deno.test("minBy() handles Map values", () => { const min = minBy(input.values(), (i) => i.age); assertEquals(min, { age: 23 }); }); + +Deno.test({ + name: "unstable.minBy() passes index to selector", + fn() { + const input = [4, 3, 2, 1]; + + const max = unstable.minBy(input, (_, index) => index); + + assertEquals(max, 4); + }, +}); diff --git a/collections/min_of_test.ts b/collections/min_of_test.ts index e379b4e65fc1..69cf651509ca 100644 --- a/collections/min_of_test.ts +++ b/collections/min_of_test.ts @@ -1,6 +1,7 @@ // Copyright 2018-2026 the Deno authors. MIT license. import { minOf } from "./min_of.ts"; import { assertEquals } from "@std/assert"; +import * as unstable from "./unstable_min_of.ts"; Deno.test("minOf() handles regular min", () => { const array = [5, 18, 35, 120]; @@ -142,3 +143,14 @@ Deno.test("minOf() handles minus infinity", () => { assertEquals(actual, -Infinity); }); + +Deno.test({ + name: "unstable.minOf() passes index to selector", + fn() { + const input = [4, 3, 2, 1]; + + const max = unstable.minOf(input, (it, index) => it * index); + + assertEquals(max, 0); + }, +}); diff --git a/collections/partition_test.ts b/collections/partition_test.ts index 85aec9f9ee3d..d0a7014b96f9 100644 --- a/collections/partition_test.ts +++ b/collections/partition_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { partition } from "./partition.ts"; +import * as unstable from "./unstable_partition.ts"; function partitionTest( input: [Array, (element: I) => boolean], @@ -112,3 +113,14 @@ Deno.test({ assertEquals(odd, [1, 3, 5]); }, }); + +Deno.test({ + name: "unstable.partition() passes index to predicate", + fn() { + const result = unstable.partition( + [2, 4, 6], + (_: number, index: number) => index % 2 === 0, + ); + assertEquals(result, [[2, 6], [4]]); + }, +}); diff --git a/collections/permutations.ts b/collections/permutations.ts index cbe34c4fbe0b..79c5e01a2ab1 100644 --- a/collections/permutations.ts +++ b/collections/permutations.ts @@ -2,7 +2,7 @@ // This module is browser compatible. /** - * Builds all possible orders of all elements in the given array + * Builds all possible orders of all elements in the given array. * Ignores equality of elements, meaning this will always return the same * number of permutations for a given length of input. * diff --git a/collections/sort_by_test.ts b/collections/sort_by_test.ts index 6984e0753b2d..b99731e4485d 100644 --- a/collections/sort_by_test.ts +++ b/collections/sort_by_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { sortBy } from "./sort_by.ts"; +import * as unstable from "./unstable_sort_by.ts"; Deno.test({ name: "sortBy() handles no mutation", @@ -331,3 +332,10 @@ Deno.test({ assertEquals(sorted.length, 10000); }, }); + +Deno.test({ + name: "unstable.sortBy() passes index to selector", + fn() { + assertEquals(unstable.sortBy([2, 3, 1], (_, index) => -index), [1, 3, 2]); + }, +}); diff --git a/collections/sum_of_test.ts b/collections/sum_of_test.ts index 21876f6c8a5a..82035e3ce17d 100644 --- a/collections/sum_of_test.ts +++ b/collections/sum_of_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { sumOf } from "./sum_of.ts"; +import * as unstable from "./unstable_sum_of.ts"; Deno.test("sumOf() handles object properties", () => { const object = [ @@ -130,3 +131,11 @@ Deno.test("sumOf() handles Infinity", () => { assertEquals(actual, Infinity); }); + +Deno.test("unstable.sumOf() passes index to selector", () => { + const array = [1, 2, 3]; + + const actual = unstable.sumOf(array, (_, index) => index); + + assertEquals(actual, 3); +}); diff --git a/collections/take_last_while_test.ts b/collections/take_last_while_test.ts index 6d032e53c840..efdcbd4a80dd 100644 --- a/collections/take_last_while_test.ts +++ b/collections/take_last_while_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { takeLastWhile } from "./take_last_while.ts"; +import * as unstable from "./unstable_take_last_while.ts"; Deno.test("takeLastWhile() handles num array", () => { const arr = [1, 2, 3, 4, 5, 6]; @@ -116,3 +117,11 @@ Deno.test("takeLastWhile() gets from last matching element from a generator", () const actual = takeLastWhile(gen(), (i) => i !== 2 && i !== 4); assertEquals(actual, [5, 6]); }); + +Deno.test("unstable.takeLastWhile() passes the index to the predicate", () => { + const arr = [1, 2, 3, 4]; + + const actual = unstable.takeLastWhile(arr, (_, index) => index > 1); + + assertEquals(actual, [3, 4]); +}); diff --git a/collections/take_while_test.ts b/collections/take_while_test.ts index 00e9bc32adce..7b4833bf3c37 100644 --- a/collections/take_while_test.ts +++ b/collections/take_while_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert"; import { takeWhile } from "./take_while.ts"; +import * as unstable from "./unstable_take_while.ts"; Deno.test("takeWhile() handles num array", () => { const arr = [1, 2, 3, 4, 5, 6]; @@ -90,3 +91,11 @@ Deno.test("takeWhile() handles a Map", () => { ["c", 3], ]); }); + +Deno.test("unstable.takeWhile() passes the index to the predicate", () => { + const arr = [1, 2, 3, 4]; + + const actual = unstable.takeWhile(arr, (_, index) => index < 1); + + assertEquals(actual, [1]); +}); diff --git a/collections/unstable_distinct_by.ts b/collections/unstable_distinct_by.ts new file mode 100644 index 000000000000..c8c5213060a8 --- /dev/null +++ b/collections/unstable_distinct_by.ts @@ -0,0 +1,58 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns all elements in the given array that produce a unique value using + * the given discriminator, with the first matching occurrence retained. + * + * Uniqueness is determined by same-value-zero equality of the returned values. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the input array. + * @typeParam D The type of the values produced by the discriminator function. + * + * @param array The array to filter for distinct elements. + * @param discriminator The function to extract the value to compare for + * uniqueness. The function receives the element and its index. + * + * @returns An array of distinct elements in the input array. + * + * @example Basic usage + * ```ts + * import { distinctBy } from "@std/collections/unstable-distinct-by"; + * import { assertEquals } from "@std/assert"; + * + * const users = [{ id: 1, name: "Anna" }, { id: 2, name: "Kim" }, { id: 1, name: "Anna again" }]; + * const uniqueUsers = distinctBy(users, (user) => user.id); + * + * assertEquals(uniqueUsers, [{ id: 1, name: "Anna" }, { id: 2, name: "Kim" }]); + * ``` + * + * @example Using the index parameter + * ```ts + * import { distinctBy } from "@std/collections/unstable-distinct-by"; + * import { assertEquals } from "@std/assert"; + * + * const items = [25, "asdf", true]; + * const result = distinctBy(items, (_, index) => index > 1); + * + * assertEquals(result, [25, true]); + * ``` + */ +export function distinctBy( + array: Iterable, + discriminator: (el: T, index: number) => D, +): T[] { + const keys = new Set(); + const result: T[] = []; + let index = 0; + for (const element of array) { + const key = discriminator(element, index++); + if (!keys.has(key)) { + keys.add(key); + result.push(element); + } + } + return result; +} diff --git a/collections/unstable_drop_last_while.ts b/collections/unstable_drop_last_while.ts new file mode 100644 index 000000000000..0a359ff706b2 --- /dev/null +++ b/collections/unstable_drop_last_while.ts @@ -0,0 +1,52 @@ +// Copyright 2018-2026 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. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the input iterable. + * + * @param iterable The iterable to drop elements from. + * @param predicate The function to test each element for a condition. The + * function receives the element and its index. + * + * @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]); + * ``` + * + * @example Using the index parameter + * ```ts + * import { dropLastWhile } from "@std/collections/unstable-drop-last-while"; + * import { assertEquals } from "@std/assert"; + * + * const array = [20, 30, 20]; + * const result = dropLastWhile(array, (_, index) => index > 1); + * + * assertEquals(result, [20, 30]); + * ``` + */ +export function dropLastWhile( + iterable: Iterable, + predicate: (el: T, index: number) => boolean, +): T[] { + const array = Array.isArray(iterable) ? iterable : Array.from(iterable); + let offset = array.length - 1; + while (offset >= 0 && predicate(array[offset]!, offset)) { + offset--; + } + return array.slice(0, offset + 1); +} diff --git a/collections/unstable_drop_while.ts b/collections/unstable_drop_while.ts new file mode 100644 index 000000000000..307e7ab44786 --- /dev/null +++ b/collections/unstable_drop_while.ts @@ -0,0 +1,62 @@ +// Copyright 2018-2026 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 iterable. + * + * @param iterable The iterable to drop elements from. + * @param predicate The function to test each element for a condition. The + * function receives the element and its index. + * + * @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]); + * ``` + * + * @example Using the index parameter + * ```ts + * import { dropWhile } from "@std/collections/unstable-drop-while"; + * import { assertEquals } from "@std/assert"; + * + * const array = [20, 30, 20]; + * const result = dropWhile(array, (_, index) => index < 1); + * + * assertEquals(result, [30, 20]); + * ``` + */ +export function dropWhile( + iterable: Iterable, + predicate: (el: T, index: number) => boolean, +): T[] { + if (Array.isArray(iterable)) { + const idx = iterable.findIndex((el, index) => !predicate(el, index)); + if (idx === -1) { + return []; + } + return iterable.slice(idx); + } + const array: T[] = []; + let index = 0; + let found = false; + for (const item of iterable) { + if (found || !predicate(item, index++)) { + found = true; + array.push(item); + } + } + return array; +} diff --git a/collections/unstable_find_single.ts b/collections/unstable_find_single.ts new file mode 100644 index 000000000000..ce07834b1775 --- /dev/null +++ b/collections/unstable_find_single.ts @@ -0,0 +1,63 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns an element if and only if that element is the only one matching the + * given condition. Returns `undefined` otherwise. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the input array. + * + * @param array The array to find a single element in. + * @param predicate The function to test each element for a condition. The + * function receives the element and its index. + * + * @returns The single element that matches the given condition or `undefined` + * if there are zero or more than one matching elements. + * + * @example Basic usage + * ```ts + * import { findSingle } from "@std/collections/unstable-find-single"; + * import { assertEquals } from "@std/assert"; + * + * const bookings = [ + * { month: "January", active: false }, + * { month: "March", active: false }, + * { month: "June", active: true }, + * ]; + * const activeBooking = findSingle(bookings, (booking) => booking.active); + * const inactiveBooking = findSingle(bookings, (booking) => !booking.active); + * + * assertEquals(activeBooking, { month: "June", active: true }); + * assertEquals(inactiveBooking, undefined); // There are two applicable items + * ``` + * + * @example Using the index parameter + * ```ts + * import { findSingle } from "@std/collections/unstable-find-single"; + * import { assertEquals } from "@std/assert"; + * + * const array = [9, 12, 13]; + * const result = findSingle(array, (_, index) => index === 1); + * + * assertEquals(result, 12); + * ``` + */ +export function findSingle( + array: Iterable, + predicate: (el: T, index: number) => boolean, +): T | undefined { + let match: T | undefined; + let found = false; + let index = 0; + for (const element of array) { + if (predicate(element, index++)) { + if (found) return undefined; + found = true; + match = element; + } + } + + return match; +} diff --git a/collections/unstable_first_not_nullish_of.ts b/collections/unstable_first_not_nullish_of.ts new file mode 100644 index 000000000000..81a80eebf334 --- /dev/null +++ b/collections/unstable_first_not_nullish_of.ts @@ -0,0 +1,50 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Applies the given selector to elements in the given array until a value is + * produced that is neither `null` nor `undefined` and returns that value. + * Returns `undefined` if no such value is produced. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the input array. + * @typeParam O The type of the value produced by the selector function. + * + * @param array The array to select a value from. + * @param selector The function to extract a value from an element. + * + * @returns The first non-`null` and non-`undefined` value produced by the + * selector function, or `undefined` if no such value is produced. + * + * @example Basic usage + * ```ts + * import { firstNotNullishOf } from "@std/collections/unstable-first-not-nullish-of"; + * import { assertEquals } from "@std/assert"; + * + * const tables = [ + * { number: 11, order: null }, + * { number: 12, order: "Soup" }, + * { number: 13, order: "Salad" }, + * ]; + * + * const nextOrder = firstNotNullishOf(tables, (table) => table.order); + * + * assertEquals(nextOrder, "Soup"); + * ``` + */ +export function firstNotNullishOf( + array: Iterable, + selector: (item: T, index: number) => O | undefined | null, +): NonNullable | undefined { + let index = 0; + for (const current of array) { + const selected = selector(current, index++); + + if (selected !== null && selected !== undefined) { + return selected as NonNullable; + } + } + + return undefined; +} diff --git a/collections/unstable_join_to_string.ts b/collections/unstable_join_to_string.ts new file mode 100644 index 000000000000..d455c63568f8 --- /dev/null +++ b/collections/unstable_join_to_string.ts @@ -0,0 +1,111 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** Options for {@linkcode joinToString}. */ +export type JoinToStringOptions = { + /** + * The string to use as a separator between the elements. + * + * @default {","} + */ + separator?: string; + /** + * The string to use as a prefix for the resulting string. + * + * @default {""} + */ + prefix?: string; + /** + * The string to use as a suffix for the resulting string. + * + * @default {""} + */ + suffix?: string; + /** + * The maximum number of elements to append. If the value is negative, all + * elements will be appended, which is the default. + * + * @default {-1} + */ + limit?: number; + /** + * The string to use as a placeholder for the truncated elements. + * + * @default {"..."} + */ + truncated?: string; +}; + +/** + * Transforms the elements in the given array to strings using the given + * selector. Joins the produced strings into one using the given `separator` + * and applying the given `prefix` and `suffix` to the whole string afterwards. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * If the array could be huge, you can specify a non-negative value of `limit`, + * in which case only the first `limit` elements will be appended, followed by + * the `truncated` string. + * + * @typeParam T The type of the elements in the input array. + * + * @param array The array to join elements from. + * @param selector The function to transform elements to strings. + * @param options The options to configure the joining. + * + * @returns The resulting string. + * + * @example Usage with options + * ```ts + * import { joinToString } from "@std/collections/unstable-join-to-string"; + * import { assertEquals } from "@std/assert"; + * + * const users = [ + * { name: "Kim" }, + * { name: "Anna" }, + * { name: "Tim" }, + * ]; + * + * const message = joinToString(users, (user) => user.name, { + * suffix: " are winners", + * prefix: "result: ", + * separator: " and ", + * limit: 1, + * truncated: "others", + * }); + * + * assertEquals(message, "result: Kim and others are winners"); + * ``` + */ +export function joinToString( + array: Iterable, + selector: (el: T, index: number) => string, + options: Readonly = {}, +): string { + const { + separator = ",", + prefix = "", + suffix = "", + limit = -1, + truncated = "...", + } = options; + + let result = ""; + + let index = 0; + for (const el of array) { + if (index > 0) { + result += separator; + } + + if (limit >= 0 && index >= limit) { + result += truncated; + break; + } + + result += selector(el, index); + index++; + } + + return prefix + result + suffix; +} diff --git a/collections/unstable_map_not_nullish.ts b/collections/unstable_map_not_nullish.ts new file mode 100644 index 000000000000..acfca67b855c --- /dev/null +++ b/collections/unstable_map_not_nullish.ts @@ -0,0 +1,52 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns a new array, containing all elements in the given array transformed + * using the given transformer, except the ones that were transformed to `null` + * or `undefined`. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the input array. + * @typeParam O The type of the elements in the output array. + * + * @param array The array to map elements from. + * @param transformer The function to transform each element. + * + * @returns A new array with all elements transformed by the given transformer, + * except the ones that were transformed to `null` or `undefined`. + * + * @example Basic usage + * ```ts + * import { mapNotNullish } from "@std/collections/unstable-map-not-nullish"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { middleName: null }, + * { middleName: "William" }, + * { middleName: undefined }, + * { middleName: "Martha" }, + * ]; + * const foundMiddleNames = mapNotNullish(people, (people) => people.middleName); + * + * assertEquals(foundMiddleNames, ["William", "Martha"]); + * ``` + */ +export function mapNotNullish( + array: Iterable, + transformer: (el: T, index: number) => O, +): NonNullable[] { + const result: NonNullable[] = []; + let index = 0; + + for (const element of array) { + const transformedElement = transformer(element, index++); + + if (transformedElement !== undefined && transformedElement !== null) { + result.push(transformedElement as NonNullable); + } + } + + return result; +} diff --git a/collections/unstable_max_by.ts b/collections/unstable_max_by.ts new file mode 100644 index 000000000000..fe3b99beb1e7 --- /dev/null +++ b/collections/unstable_max_by.ts @@ -0,0 +1,181 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns the first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { maxBy } from "@std/collections/unstable-max-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", age: 34 }, + * { name: "Kim", age: 42 }, + * { name: "John", age: 23 }, + * ]; + * + * const personWithMaxAge = maxBy(people, (person) => person.age); + * + * assertEquals(personWithMaxAge, { name: "Kim", age: 42 }); + * ``` + */ +export function maxBy( + array: Iterable, + selector: (el: T, index: number) => number, +): T | undefined; +/** + * Returns the first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { maxBy } from "@std/collections/unstable-max-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna" }, + * { name: "Kim" }, + * { name: "John" }, + * ]; + * + * const personWithMaxName = maxBy(people, (person) => person.name); + * + * assertEquals(personWithMaxName, { name: "Kim" }); + * ``` + */ +export function maxBy( + array: Iterable, + selector: (el: T, index: number) => string, +): T | undefined; +/** + * Returns the first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { maxBy } from "@std/collections/unstable-max-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", age: 34n }, + * { name: "Kim", age: 42n }, + * { name: "John", age: 23n }, + * ]; + * + * const personWithMaxAge = maxBy(people, (person) => person.age); + * + * assertEquals(personWithMaxAge, { name: "Kim", age: 42n }); + * ``` + */ +export function maxBy( + array: Iterable, + selector: (el: T, index: number) => bigint, +): T | undefined; +/** + * Returns the first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { maxBy } from "@std/collections/unstable-max-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", startedAt: new Date("2020-01-01") }, + * { name: "Kim", startedAt: new Date("2021-03-01") }, + * { name: "John", startedAt: new Date("2020-03-01") }, + * ]; + * + * const personWithLastStartedAt = maxBy(people, (person) => person.startedAt); + * + * assertEquals(personWithLastStartedAt, { name: "Kim", startedAt: new Date("2021-03-01") }); + * ``` + */ +export function maxBy( + array: Iterable, + selector: (el: T, index: number) => Date, +): T | undefined; +export function maxBy( + array: Iterable, + selector: + | ((el: T, index: number) => number) + | ((el: T, index: number) => string) + | ((el: T, index: number) => bigint) + | ((el: T, index: number) => Date), +): T | undefined { + if (Array.isArray(array)) { + const length = array.length; + if (length === 0) return undefined; + + let max: T = array[0]!; + let maxValue = selector(max, 0); + + for (let i = 1; i < length; i++) { + const current = array[i]!; + const currentValue = selector(current, i); + if (currentValue > maxValue) { + max = current; + maxValue = currentValue; + } + } + + return max; + } + + const iter = array[Symbol.iterator](); + const first = iter.next(); + + if (first.done) return undefined; + + let index = 0; + let max: T = first.value; + let maxValue = selector(max, index++); + + let next = iter.next(); + while (!next.done) { + const currentValue = selector(next.value, index++); + if (currentValue > maxValue) { + max = next.value; + maxValue = currentValue; + } + next = iter.next(); + } + + return max; +} diff --git a/collections/unstable_max_of.ts b/collections/unstable_max_of.ts new file mode 100644 index 000000000000..897a28017713 --- /dev/null +++ b/collections/unstable_max_of.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Applies the given selector to all elements of the provided collection and + * returns the max value of all elements. If an empty array is provided the + * function will return undefined. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The largest value of the given function or undefined if there are no + * elements. + * + * @example Basic usage + * ```ts + * import { maxOf } from "@std/collections/unstable-max-of"; + * import { assertEquals } from "@std/assert"; + * + * const inventory = [ + * { name: "mustard", count: 2 }, + * { name: "soy", count: 4 }, + * { name: "tomato", count: 32 }, + * ]; + * + * const maxCount = maxOf(inventory, (item) => item.count); + * + * assertEquals(maxCount, 32); + * ``` + */ +export function maxOf( + array: Iterable, + selector: (el: T, index: number) => number, +): number | undefined; +/** + * Applies the given selector to all elements of the provided collection and + * returns the max value of all elements. If an empty array is provided the + * function will return undefined. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the maximum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the largest value of the given function or + * undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { maxOf } from "@std/collections/unstable-max-of"; + * import { assertEquals } from "@std/assert"; + * + * const inventory = [ + * { name: "mustard", count: 2n }, + * { name: "soy", count: 4n }, + * { name: "tomato", count: 32n }, + * ]; + * + * const maxCount = maxOf(inventory, (i) => i.count); + * + * assertEquals(maxCount, 32n); + * ``` + */ +export function maxOf( + array: Iterable, + selector: (el: T, index: number) => bigint, +): bigint | undefined; +export function maxOf< + T, + S extends + | ((el: T, index: number) => number) + | ((el: T, index: number) => bigint), +>( + array: Iterable, + selector: S, +): ReturnType | undefined { + if (Array.isArray(array)) { + const length = array.length; + if (length === 0) return undefined; + + let max = selector(array[0]!, 0) as ReturnType; + if (Number.isNaN(max)) return max; + + for (let i = 1; i < length; i++) { + const currentValue = selector(array[i]!, i) as ReturnType; + if (currentValue > max) { + max = currentValue; + } else if (Number.isNaN(currentValue)) { + return currentValue; + } + } + + return max; + } + + let index = 0; + const iter = array[Symbol.iterator](); + const first = iter.next(); + + if (first.done) return undefined; + + let max = selector(first.value, index++) as ReturnType; + if (Number.isNaN(max)) return max; + + let next = iter.next(); + while (!next.done) { + const currentValue = selector(next.value, index++) as ReturnType; + if (currentValue > max) { + max = currentValue; + } else if (Number.isNaN(currentValue)) { + return currentValue; + } + next = iter.next(); + } + + return max; +} diff --git a/collections/unstable_min_by.ts b/collections/unstable_min_by.ts new file mode 100644 index 000000000000..fac9353b2d94 --- /dev/null +++ b/collections/unstable_min_by.ts @@ -0,0 +1,179 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns the first element that is the smallest value of the given function or + * undefined if there are no elements. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the smallest value of the given function + * or undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { minBy } from "@std/collections/unstable-min-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", age: 34 }, + * { name: "Kim", age: 42 }, + * { name: "John", age: 23 }, + * ]; + * + * const personWithMinAge = minBy(people, (i) => i.age); + * + * assertEquals(personWithMinAge, { name: "John", age: 23 }); + * ``` + */ +export function minBy( + array: Iterable, + selector: (el: T, index: number) => number, +): T | undefined; +/** + * Returns the first element that is the smallest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the smallest value of the given function + * or undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { minBy } from "@std/collections/unstable-min-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna" }, + * { name: "Kim" }, + * { name: "John" }, + * ]; + * + * const personWithMinName = minBy(people, (person) => person.name); + * + * assertEquals(personWithMinName, { name: "Anna" }); + * ``` + */ +export function minBy( + array: Iterable, + selector: (el: T, index: number) => string, +): T | undefined; +/** + * Returns the first element that is the smallest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the smallest value of the given function + * or undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { minBy } from "@std/collections/unstable-min-by"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", age: 34n }, + * { name: "Kim", age: 42n }, + * { name: "John", age: 23n }, + * ]; + * + * const personWithMinAge = minBy(people, (i) => i.age); + * + * assertEquals(personWithMinAge, { name: "John", age: 23n }); + * ``` + */ +export function minBy( + array: Iterable, + selector: (el: T, index: number) => bigint, +): T | undefined; +/** + * Returns the first element that is the smallest value of the given function or + * undefined if there are no elements. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the smallest value of the given function + * or undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { minBy } from "@std/collections/unstable-min-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("2019-01-01") }, + * ]; + * + * const personWithMinStartedAt = minBy(people, (person) => person.startedAt); + * ``` + */ +export function minBy( + array: Iterable, + selector: (el: T, index: number) => Date, +): T | undefined; +export function minBy( + array: Iterable, + selector: + | ((el: T, index: number) => number) + | ((el: T, index: number) => string) + | ((el: T, index: number) => bigint) + | ((el: T, index: number) => Date), +): T | undefined { + if (Array.isArray(array)) { + const length = array.length; + if (length === 0) return undefined; + + let min: T = array[0]!; + let minValue = selector(min, 0); + + for (let i = 1; i < length; i++) { + const current = array[i]!; + const currentValue = selector(current, i); + if (currentValue < minValue) { + min = current; + minValue = currentValue; + } + } + + return min; + } + + let index = 0; + const iter = array[Symbol.iterator](); + const first = iter.next(); + + if (first.done) return undefined; + + let min: T = first.value; + let minValue = selector(min, index++); + + let next = iter.next(); + while (!next.done) { + const currentValue = selector(next.value, index++); + if (currentValue < minValue) { + min = next.value; + minValue = currentValue; + } + next = iter.next(); + } + + return min; +} diff --git a/collections/unstable_min_of.ts b/collections/unstable_min_of.ts new file mode 100644 index 000000000000..85d8ae7bead5 --- /dev/null +++ b/collections/unstable_min_of.ts @@ -0,0 +1,121 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Applies the given selector to all elements of the given collection and + * returns the min value of all elements. If an empty array is provided the + * function will return undefined. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The smallest value of the given function or undefined if there are + * no elements. + * + * @example Basic usage + * ```ts + * import { minOf } from "@std/collections/unstable-min-of"; + * import { assertEquals } from "@std/assert"; + * + * const inventory = [ + * { name: "mustard", count: 2 }, + * { name: "soy", count: 4 }, + * { name: "tomato", count: 32 }, + * ]; + * + * const minCount = minOf(inventory, (item) => item.count); + * + * assertEquals(minCount, 2); + * ``` + */ +export function minOf( + array: Iterable, + selector: (el: T, index: number) => number, +): number | undefined; +/** + * Applies the given selector to all elements of the given collection and + * returns the min value of all elements. If an empty array is provided the + * function will return undefined. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to find the minimum element in. + * @param selector The function to get the value to compare from each element. + * + * @returns The first element that is the smallest value of the given function + * or undefined if there are no elements. + * + * @example Basic usage + * ```ts + * import { minOf } from "@std/collections/unstable-min-of"; + * import { assertEquals } from "@std/assert"; + * + * const inventory = [ + * { name: "mustard", count: 2n }, + * { name: "soy", count: 4n }, + * { name: "tomato", count: 32n }, + * ]; + * + * const minCount = minOf(inventory, (item) => item.count); + * + * assertEquals(minCount, 2n); + * ``` + */ +export function minOf( + array: Iterable, + selector: (el: T, index: number) => bigint, +): bigint | undefined; +export function minOf< + T, + S extends + | ((el: T, index: number) => number) + | ((el: T, index: number) => bigint), +>( + array: Iterable, + selector: S, +): ReturnType | undefined { + if (Array.isArray(array)) { + const length = array.length; + if (length === 0) return undefined; + + let min = selector(array[0]!, 0) as ReturnType; + if (Number.isNaN(min)) return min; + + for (let i = 1; i < length; i++) { + const currentValue = selector(array[i]!, i) as ReturnType; + if (currentValue < min) { + min = currentValue; + } else if (Number.isNaN(currentValue)) { + return currentValue; + } + } + + return min; + } + + let index = 0; + const iter = array[Symbol.iterator](); + const first = iter.next(); + + if (first.done) return undefined; + + let min = selector(first.value, index++) as ReturnType; + if (Number.isNaN(min)) return min; + + let next = iter.next(); + while (!next.done) { + const currentValue = selector(next.value, index++) as ReturnType; + if (currentValue < min) { + min = currentValue; + } else if (Number.isNaN(currentValue)) { + return currentValue; + } + next = iter.next(); + } + + return min; +} diff --git a/collections/unstable_partition.ts b/collections/unstable_partition.ts new file mode 100644 index 000000000000..dfce5c07d0e8 --- /dev/null +++ b/collections/unstable_partition.ts @@ -0,0 +1,89 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Returns a tuple of two arrays with the first one containing all elements in + * the given array that match the given predicate and the second one + * containing all that do not. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * This version of the function is a type-guard version of the function. It + * allows you to specify a type-guard predicate function that narrows the type + * of the elements in the first output array. + * + * @typeParam T The type of the elements in the array. + * @typeParam U The type of the elements that match the predicate. + * + * @param array The array to partition. + * @param predicate The type-guard predicate function to determine which array + * an element belongs to. + * + * @returns A tuple of two arrays. The first array contains all elements that + * match the predicate, the second contains all elements that do not. + * + * @example Basic usage + * ```ts + * import { partition } from "@std/collections/unstable-partition"; + * import { assertEquals } from "@std/assert"; + * + * const mixed = [1, "a", 2, "b"]; + * const isString = (x: unknown): x is string => typeof x === "string"; + * const [strings, others] = partition(mixed, isString); + * + * assertEquals(strings, ["a", "b"]); + * assertEquals(others, [1, 2]); + * ``` + */ +export function partition( + array: Iterable, + predicate: (el: T) => el is U, +): [U[], Exclude[]]; +/** + * Returns a tuple of two arrays with the first one containing all elements in + * the given array that match the given predicate and the second one + * containing all that do not. + * + * @typeParam T The type of the elements in the array. + * + * @param array The array to partition. + * @param predicate The predicate function to determine which array an element + * belongs to. + * + * @returns A tuple of two arrays. The first array contains all elements that + * match the predicate, the second contains all elements that do not. + * + * @example Basic usage + * ```ts + * import { partition } from "@std/collections/unstable-partition"; + * import { assertEquals } from "@std/assert"; + * + * const numbers = [5, 6, 7, 8, 9]; + * const [even, odd] = partition(numbers, (it) => it % 2 === 0); + * + * assertEquals(even, [6, 8]); + * assertEquals(odd, [5, 7, 9]); + * ``` + */ +export function partition( + array: Iterable, + predicate: (el: T, index: number) => boolean, +): [T[], T[]]; +export function partition( + array: Iterable, + predicate: (el: unknown, index: number) => boolean, +): [unknown[], unknown[]] { + const matches: Array = []; + const rest: Array = []; + let index = 0; + + for (const element of array) { + if (predicate(element, index++)) { + matches.push(element); + } else { + rest.push(element); + } + } + + return [matches, rest]; +} diff --git a/collections/unstable_sort_by.ts b/collections/unstable_sort_by.ts new file mode 100644 index 000000000000..15116af68ee9 --- /dev/null +++ b/collections/unstable_sort_by.ts @@ -0,0 +1,156 @@ +// Copyright 2018-2026 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; +}; + +/** + * 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/unstable-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/unstable-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/unstable-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/unstable-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, index: number) => number) + | ((el: T, index: number) => string) + | ((el: T, index: number) => bigint) + | ((el: T, index: number) => Date), + options?: SortByOptions, +): T[] { + const array = Array.isArray(iterator) ? iterator : Array.from(iterator); + const len = array.length; + const selected: (string | number | bigint | Date)[] = new Array(len); + const indices: number[] = new Array(len); + + for (let i = 0; i < len; i++) { + selected[i] = selector(array[i]!, i); + indices[i] = i; + } + + const order = options?.order === "desc" ? -1 : 1; + + indices.sort((ia, ib) => { + const a = selected[ia]!; + const b = selected[ib]!; + + if (Number.isNaN(a)) return order; + if (Number.isNaN(b)) return -order; + + return order * (a > b ? 1 : a < b ? -1 : 0); + }); + + const result: T[] = new Array(len); + for (let i = 0; i < len; i++) { + result[i] = array[indices[i]!]!; + } + return result; +} diff --git a/collections/unstable_sum_of.ts b/collections/unstable_sum_of.ts new file mode 100644 index 000000000000..4b760a2207c5 --- /dev/null +++ b/collections/unstable_sum_of.ts @@ -0,0 +1,45 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Applies the given selector to all elements in the given collection and + * calculates the sum of the results. + * + * @experimental **UNSTABLE**: New API, yet to be vetted. + * + * @typeParam T The type of the array elements. + * + * @param array The array to calculate the sum of. + * @param selector The selector function to get the value to sum. + * + * @returns The sum of all elements in the collection. + * + * @example Basic usage + * ```ts + * import { sumOf } from "@std/collections/unstable-sum-of"; + * import { assertEquals } from "@std/assert"; + * + * const people = [ + * { name: "Anna", age: 34 }, + * { name: "Kim", age: 42 }, + * { name: "John", age: 23 }, + * ]; + * + * const totalAge = sumOf(people, (person) => person.age); + * + * assertEquals(totalAge, 99); + * ``` + */ +export function sumOf( + array: Iterable, + selector: (el: T, index: number) => number, +): number { + let sum = 0; + let index = 0; + + for (const i of array) { + sum += selector(i, index++); + } + + return sum; +} diff --git a/collections/unstable_take_last_while.ts b/collections/unstable_take_last_while.ts new file mode 100644 index 000000000000..97a737c29f5e --- /dev/null +++ b/collections/unstable_take_last_while.ts @@ -0,0 +1,50 @@ +// Copyright 2018-2026 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, index: number) => boolean, +): T[] { + if (Array.isArray(iterable)) { + let offset = iterable.length; + while (0 < offset && predicate(iterable[offset - 1] as T, offset - 1)) { + offset--; + } + return iterable.slice(offset); + } + let index = 0; + const result: T[] = []; + for (const el of iterable) { + if (predicate(el, index++)) { + result.push(el); + } else { + result.length = 0; + } + } + return result; +} diff --git a/collections/unstable_take_while.ts b/collections/unstable_take_while.ts new file mode 100644 index 000000000000..23e6b92e20a5 --- /dev/null +++ b/collections/unstable_take_while.ts @@ -0,0 +1,44 @@ +// Copyright 2018-2026 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/unstable-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, index: number) => boolean, +): T[] { + let index = 0; + const result: T[] = []; + for (const element of iterable) { + if (!predicate(element, index++)) { + break; + } + result.push(element); + } + return result; +}