diff --git a/collections/count_by.ts b/collections/count_by.ts new file mode 100644 index 000000000000..7d3a63512913 --- /dev/null +++ b/collections/count_by.ts @@ -0,0 +1,44 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Counts the occurrences of each key returned by the given function. + * + * @typeParam T The type of the array elements + * @typeParam K The type of the keys + * + * @param array The array to count + * @param keyFn The function that returns the key for each element + * + * @returns An object mapping keys to their counts + * + * @example Basic usage + * ```ts + * import { countBy } from "@std/collections/count-by"; + * import { assertEquals } from "@std/assert"; + * + * const pets = [ + * { type: "dog", name: "Fido" }, + * { type: "cat", name: "Whiskers" }, + * { type: "dog", name: "Rover" }, + * ]; + * + * const counts = countBy(pets, (pet) => pet.type); + * assertEquals(counts, { dog: 2, cat: 1 }); + * ``` + */ +export function countBy( + array: readonly T[], + keyFn: (element: T) => K, +): Record { + const result = {} as Record; + for (const element of array) { + const key = keyFn(element); + if (Object.prototype.hasOwnProperty.call(result, key)) { + result[key]!++; + } else { + result[key] = 1; + } + } + return result; +} diff --git a/collections/count_by_test.ts b/collections/count_by_test.ts new file mode 100644 index 000000000000..c14991ba5f94 --- /dev/null +++ b/collections/count_by_test.ts @@ -0,0 +1,32 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +import { assertEquals } from "@std/assert"; +import { countBy } from "./count_by.ts"; + +Deno.test({ + name: "countBy() counts elements by key", + fn() { + const pets = [ + { type: "dog", name: "Fido" }, + { type: "cat", name: "Whiskers" }, + { type: "dog", name: "Rover" }, + ]; + assertEquals(countBy(pets, (pet) => pet.type), { dog: 2, cat: 1 }); + }, +}); + +Deno.test({ + name: "countBy() handles empty array", + fn() { + assertEquals(countBy([], (x: number) => x % 2), {}); + }, +}); + +Deno.test({ + name: "countBy() counts numbers by parity", + fn() { + assertEquals( + countBy([1, 2, 3, 4, 5, 6], (n) => n % 2 === 0 ? "even" : "odd"), + { odd: 3, even: 3 }, + ); + }, +}); diff --git a/collections/deno.json b/collections/deno.json index 81a054b93de0..741aaa2d9457 100644 --- a/collections/deno.json +++ b/collections/deno.json @@ -7,6 +7,7 @@ "./associate-by": "./associate_by.ts", "./associate-with": "./associate_with.ts", "./chunk": "./chunk.ts", + "./count-by": "./count_by.ts", "./deep-merge": "./deep_merge.ts", "./distinct": "./distinct.ts", "./distinct-by": "./distinct_by.ts", @@ -38,6 +39,7 @@ "./partition-entries": "./partition_entries.ts", "./permutations": "./permutations.ts", "./pick": "./pick.ts", + "./range": "./range.ts", "./reduce-groups": "./reduce_groups.ts", "./running-reduce": "./running_reduce.ts", "./sample": "./sample.ts", diff --git a/collections/mod.ts b/collections/mod.ts index 5c6a12a2a5ea..d9e05aa98893 100644 --- a/collections/mod.ts +++ b/collections/mod.ts @@ -32,6 +32,7 @@ export * from "./aggregate_groups.ts"; export * from "./associate_by.ts"; export * from "./associate_with.ts"; export * from "./chunk.ts"; +export * from "./count_by.ts"; export * from "./deep_merge.ts"; export * from "./distinct.ts"; export * from "./distinct_by.ts"; @@ -63,6 +64,7 @@ export * from "./partition.ts"; export * from "./partition_entries.ts"; export * from "./permutations.ts"; export * from "./pick.ts"; +export * from "./range.ts"; export * from "./reduce_groups.ts"; export * from "./running_reduce.ts"; export * from "./sample.ts"; diff --git a/collections/range.ts b/collections/range.ts new file mode 100644 index 000000000000..fc09edb15e3b --- /dev/null +++ b/collections/range.ts @@ -0,0 +1,64 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** Options for {@linkcode range}. */ +export type RangeOptions = { + /** + * The step value. + * @default {1 or -1} + */ + step?: number; +}; + +/** + * Returns an array of numbers progressing from start up to but not including + * end, with an optional step value. + * + * If only one argument is provided, it is treated as the end value with start + * defaulting to 0. + * + * @param startOrEnd The start value (or end if only one arg) + * @param end The end value (exclusive) + * @param options Options for the range + * + * @returns An array of numbers + * + * @example Basic usage + * ```ts + * import { range } from "@std/collections/range"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals(range(5), [0, 1, 2, 3, 4]); + * assertEquals(range(1, 5), [1, 2, 3, 4]); + * assertEquals(range(0, 10, { step: 2 }), [0, 2, 4, 6, 8]); + * assertEquals(range(5, 0, { step: -1 }), [5, 4, 3, 2, 1]); + * ``` + */ +export function range( + startOrEnd: number, + end?: number, + options?: RangeOptions, +): number[] { + const start = end === undefined ? 0 : startOrEnd; + const stop = end === undefined ? startOrEnd : end; + const stepValue = options?.step ?? (stop < start ? -1 : 1); + + if (stepValue === 0) { + throw new RangeError("`step` must not be zero"); + } + if (!Number.isFinite(start) || !Number.isFinite(stop)) { + throw new RangeError("`start` and `end` must be finite numbers"); + } + + const result: number[] = []; + if (stepValue > 0) { + for (let i = start; i < stop; i += stepValue) { + result.push(i); + } + } else { + for (let i = start; i > stop; i += stepValue) { + result.push(i); + } + } + return result; +} diff --git a/collections/range_test.ts b/collections/range_test.ts new file mode 100644 index 000000000000..51593a6ff1b7 --- /dev/null +++ b/collections/range_test.ts @@ -0,0 +1,68 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +import { assertEquals, assertThrows } from "@std/assert"; +import { range } from "./range.ts"; + +Deno.test({ + name: "range() generates sequence with only end argument", + fn() { + assertEquals(range(5), [0, 1, 2, 3, 4]); + }, +}); + +Deno.test({ + name: "range() generates sequence with start and end", + fn() { + assertEquals(range(1, 5), [1, 2, 3, 4]); + }, +}); + +Deno.test({ + name: "range() generates sequence with step", + fn() { + assertEquals(range(0, 10, { step: 2 }), [0, 2, 4, 6, 8]); + }, +}); + +Deno.test({ + name: "range() generates descending sequence", + fn() { + assertEquals(range(5, 0, { step: -1 }), [5, 4, 3, 2, 1]); + }, +}); + +Deno.test({ + name: "range() returns empty array when start equals end", + fn() { + assertEquals(range(5, 5), []); + }, +}); + +Deno.test({ + name: "range() returns empty array when start <= end for descending", + fn() { + assertEquals(range(0, 0, { step: -1 }), []); + assertEquals(range(1, 2, { step: -1 }), []); + }, +}); + +Deno.test({ + name: "range() throws on step = 0", + fn() { + assertThrows( + () => range(0, 5, { step: 0 }), + RangeError, + "`step` must not be zero", + ); + }, +}); + +Deno.test({ + name: "range() throws on non-finite numbers", + fn() { + assertThrows( + () => range(Infinity, 5), + RangeError, + "`start` and `end` must be finite numbers", + ); + }, +}); diff --git a/text/deno.json b/text/deno.json index 917a67dffff8..b3e0b65a103b 100644 --- a/text/deno.json +++ b/text/deno.json @@ -5,18 +5,21 @@ ".": "./mod.ts", "./closest-string": "./closest_string.ts", "./compare-similarity": "./compare_similarity.ts", - "./unstable-dedent": "./unstable_dedent.ts", + "./is-alpha": "./is_alpha.ts", + "./is-alphanumeric": "./is_alphanumeric.ts", + "./is-numeric": "./is_numeric.ts", "./levenshtein-distance": "./levenshtein_distance.ts", + "./to-camel-case": "./to_camel_case.ts", + "./to-kebab-case": "./to_kebab_case.ts", + "./to-pascal-case": "./to_pascal_case.ts", + "./to-snake-case": "./to_snake_case.ts", + "./unstable-dedent": "./unstable_dedent.ts", "./unstable-longest-common-prefix": "./unstable_longest_common_prefix.ts", "./unstable-reverse": "./unstable_reverse.ts", "./unstable-slugify": "./unstable_slugify.ts", "./unstable-trim-by": "./unstable_trim_by.ts", - "./to-camel-case": "./to_camel_case.ts", "./unstable-to-constant-case": "./unstable_to_constant_case.ts", - "./to-kebab-case": "./to_kebab_case.ts", - "./to-pascal-case": "./to_pascal_case.ts", "./unstable-to-sentence-case": "./unstable_to_sentence_case.ts", - "./to-snake-case": "./to_snake_case.ts", "./unstable-to-title-case": "./unstable_to_title_case.ts", "./unstable-truncate": "./unstable_truncate.ts", "./word-similarity-sort": "./word_similarity_sort.ts" diff --git a/text/is_alpha.ts b/text/is_alpha.ts new file mode 100644 index 000000000000..b468c2da782a --- /dev/null +++ b/text/is_alpha.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Checks if a string contains only alphabetic characters. + * + * @param input The string to check + * @returns `true` if the string contains only alphabetic characters + * + * @example Usage + * ```ts + * import { isAlpha } from "@std/text/is-alpha"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals(isAlpha("hello"), true); + * assertEquals(isAlpha("hello123"), false); + * ``` + */ +export function isAlpha(input: string): boolean { + return /^[A-Za-z]+$/.test(input); +} diff --git a/text/is_alpha_test.ts b/text/is_alpha_test.ts new file mode 100644 index 000000000000..8ea2e82373c6 --- /dev/null +++ b/text/is_alpha_test.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +import { assertEquals } from "@std/assert"; +import { isAlpha } from "./is_alpha.ts"; + +Deno.test("isAlpha() returns true for alphabetic strings", () => { + assertEquals(isAlpha("hello"), true); + assertEquals(isAlpha("HELLO"), true); +}); + +Deno.test("isAlpha() returns false for strings with numbers", () => { + assertEquals(isAlpha("hello123"), false); +}); + +Deno.test("isAlpha() returns false for strings with special characters", () => { + assertEquals(isAlpha("hello world"), false); + assertEquals(isAlpha("hello!"), false); +}); + +Deno.test("isAlpha() returns false for empty string", () => { + assertEquals(isAlpha(""), false); +}); diff --git a/text/is_alphanumeric.ts b/text/is_alphanumeric.ts new file mode 100644 index 000000000000..3aa2669d68e6 --- /dev/null +++ b/text/is_alphanumeric.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Checks if a string contains only alphabetic and numeric characters. + * + * @param input The string to check + * @returns `true` if the string contains only alphanumeric characters + * + * @example Usage + * ```ts + * import { isAlphanumeric } from "@std/text/is-alphanumeric"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals(isAlphanumeric("hello123"), true); + * assertEquals(isAlphanumeric("hello 123"), false); + * ``` + */ +export function isAlphanumeric(input: string): boolean { + return /^[A-Za-z0-9]+$/.test(input); +} diff --git a/text/is_alphanumeric_test.ts b/text/is_alphanumeric_test.ts new file mode 100644 index 000000000000..1551d75834f2 --- /dev/null +++ b/text/is_alphanumeric_test.ts @@ -0,0 +1,19 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +import { assertEquals } from "@std/assert"; +import { isAlphanumeric } from "./is_alphanumeric.ts"; + +Deno.test("isAlphanumeric() returns true for alphanumeric strings", () => { + assertEquals(isAlphanumeric("hello123"), true); + assertEquals(isAlphanumeric("abc"), true); + assertEquals(isAlphanumeric("123"), true); +}); + +Deno.test("isAlphanumeric() returns false for strings with special characters", () => { + assertEquals(isAlphanumeric("hello 123"), false); + assertEquals(isAlphanumeric("hello!"), false); +}); + +Deno.test("isAlphanumeric() returns false for empty string", () => { + assertEquals(isAlphanumeric(""), false); +}); diff --git a/text/is_numeric.ts b/text/is_numeric.ts new file mode 100644 index 000000000000..3d9e78bf639f --- /dev/null +++ b/text/is_numeric.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// This module is browser compatible. + +/** + * Checks if a string contains only numeric characters. + * + * @param input The string to check + * @returns `true` if the string contains only numeric characters + * + * @example Usage + * ```ts + * import { isNumeric } from "@std/text/is-numeric"; + * import { assertEquals } from "@std/assert"; + * + * assertEquals(isNumeric("123"), true); + * assertEquals(isNumeric("123abc"), false); + * ``` + */ +export function isNumeric(input: string): boolean { + return /^[0-9]+$/.test(input); +} diff --git a/text/is_numeric_test.ts b/text/is_numeric_test.ts new file mode 100644 index 000000000000..e78aa26cb9a6 --- /dev/null +++ b/text/is_numeric_test.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2026 the Deno authors. MIT license. + +import { assertEquals } from "@std/assert"; +import { isNumeric } from "./is_numeric.ts"; + +Deno.test("isNumeric() returns true for numeric strings", () => { + assertEquals(isNumeric("123"), true); + assertEquals(isNumeric("0"), true); +}); + +Deno.test("isNumeric() returns false for strings with letters", () => { + assertEquals(isNumeric("123abc"), false); + assertEquals(isNumeric("abc"), false); +}); + +Deno.test("isNumeric() returns false for strings with special characters", () => { + assertEquals(isNumeric("12 3"), false); +}); + +Deno.test("isNumeric() returns false for empty string", () => { + assertEquals(isNumeric(""), false); +}); diff --git a/text/mod.ts b/text/mod.ts index f232dc488a8a..3f14a0e3cd45 100644 --- a/text/mod.ts +++ b/text/mod.ts @@ -19,11 +19,14 @@ * @module */ -export * from "./levenshtein_distance.ts"; export * from "./closest_string.ts"; export * from "./compare_similarity.ts"; -export * from "./word_similarity_sort.ts"; +export * from "./is_alpha.ts"; +export * from "./is_alphanumeric.ts"; +export * from "./is_numeric.ts"; +export * from "./levenshtein_distance.ts"; export * from "./to_camel_case.ts"; export * from "./to_kebab_case.ts"; export * from "./to_pascal_case.ts"; export * from "./to_snake_case.ts"; +export * from "./word_similarity_sort.ts";