ts-data-forge
Version:
[](https://www.npmjs.com/package/ts-data-forge) [](https://www.npmjs.com/package/ts-data-forge) [ • 73.6 kB
JavaScript
import '../collections/imap-mapped.mjs';
import { IMap } from '../collections/imap.mjs';
import '../collections/iset-mapped.mjs';
import '../collections/iset.mjs';
import { isString, isUndefined } from '../guard/is-type.mjs';
import { Optional } from '../functional/optional.mjs';
import { pipe } from '../functional/pipe.mjs';
import { Result } from '../functional/result.mjs';
import { range } from '../iterator/range.mjs';
import '../number/branded-types/finite-number.mjs';
import '../number/branded-types/int.mjs';
import '../number/branded-types/int16.mjs';
import '../number/branded-types/int32.mjs';
import '../number/branded-types/non-negative-finite-number.mjs';
import '../number/branded-types/non-negative-int16.mjs';
import '../number/branded-types/non-negative-int32.mjs';
import '../number/branded-types/non-zero-finite-number.mjs';
import '../number/branded-types/non-zero-int.mjs';
import '../number/branded-types/non-zero-int16.mjs';
import '../number/branded-types/non-zero-int32.mjs';
import '../number/branded-types/non-zero-safe-int.mjs';
import '../number/branded-types/non-zero-uint16.mjs';
import '../number/branded-types/non-zero-uint32.mjs';
import '../number/branded-types/positive-finite-number.mjs';
import '../number/branded-types/positive-int.mjs';
import '../number/branded-types/positive-int16.mjs';
import '../number/branded-types/positive-int32.mjs';
import '../number/branded-types/positive-safe-int.mjs';
import '../number/branded-types/positive-uint16.mjs';
import { asPositiveUint32 } from '../number/branded-types/positive-uint32.mjs';
import '../number/branded-types/safe-int.mjs';
import '../number/branded-types/safe-uint.mjs';
import '../number/branded-types/uint.mjs';
import '../number/branded-types/uint16.mjs';
import { Uint32, asUint32 } from '../number/branded-types/uint32.mjs';
import '../number/enum/int8.mjs';
import '../number/enum/uint8.mjs';
import { Num } from '../number/num.mjs';
import '../number/refined-number-utils.mjs';
import { castMutable } from '../others/cast-mutable.mjs';
import { tp } from '../others/tuple.mjs';
import { unknownToString } from '../others/unknown-to-string.mjs';
/**
* A comprehensive, immutable utility library for array manipulations in TypeScript.
* Provides a wide range of functions for array creation, validation, transformation,
* reduction, slicing, set operations, and more, with a focus on type safety and
* leveraging TypeScript's type inference capabilities.
* All functions operate on `readonly` arrays and return new `readonly` arrays,
* ensuring immutability.
*/
var Arr;
(function (Arr) {
/**
* Returns the size (length) of an array as a type-safe branded integer.
*
* This function provides the array length with enhanced type safety through branded types:
* - For arrays known to be non-empty at compile time: returns `PositiveNumber & SizeType.Arr`
* - For general arrays that may be empty: returns `SizeType.Arr` (branded Uint32)
*
* The returned value is always a non-negative integer that can be safely used for array indexing
* and size comparisons. The branded type prevents common integer overflow issues and provides
* better type checking than plain numbers.
*
* @template Ar The exact type of the input array, used for precise return type inference.
* @param array The array to measure. Can be any readonly array type.
* @returns The length of the array as a branded type:
* - `IntersectBrand<PositiveNumber, SizeType.Arr>` for known non-empty arrays
* - `SizeType.Arr` for general arrays (branded Uint32, may be 0)
*
* @example
* ```typescript
* // Known non-empty arrays get positive branded type
* const tuple = [1, 2, 3] as const;
* const tupleSize = Arr.size(tuple);
* // Type: IntersectBrand<PositiveNumber, SizeType.Arr>
* // Value: 3 (branded, guaranteed positive)
*
* const nonEmpty: NonEmptyArray<string> = ['a', 'b'];
* const nonEmptySize = Arr.size(nonEmpty);
* // Type: IntersectBrand<PositiveNumber, SizeType.Arr>
* // Guaranteed to be > 0
*
* // General arrays may be empty, get regular branded type
* const generalArray: number[] = [1, 2, 3];
* const generalSize = Arr.size(generalArray);
* // Type: SizeType.Arr (branded Uint32)
* // May be 0 or positive
*
* // Empty arrays
* const emptyArray = [] as const;
* const emptySize = Arr.size(emptyArray);
* // Type: SizeType.Arr
* // Value: 0 (branded)
*
* // Runtime arrays with unknown content
* const dynamicArray = Array.from({ length: Math.random() * 10 }, (_, i) => i);
* const dynamicSize = Arr.size(dynamicArray);
* // Type: SizeType.Arr (may be 0)
*
* // Using size for safe operations
* const data = [10, 20, 30];
* const dataSize = Arr.size(data);
*
* // Safe for array creation
* const indices = Arr.seq(dataSize); // Creates [0, 1, 2]
* const zeros = Arr.zeros(dataSize); // Creates [0, 0, 0]
*
* // Functional composition
* const arrays = [
* [1, 2],
* [3, 4, 5],
* [],
* [6]
* ];
* const sizes = arrays.map(Arr.size); // [2, 3, 0, 1] (all branded)
* const totalElements = sizes.reduce(Uint32.add, 0); // 6
*
* // Type guards work with size
* if (Arr.size(data) > 0) {
* // TypeScript knows data is non-empty here
* const firstElement = data[0]; // Safe access
* }
*
* // Type inference examples
* expectType<typeof tupleSize, IntersectBrand<PositiveNumber, SizeType.Arr>>('=');
* expectType<typeof generalSize, SizeType.Arr>('=');
* expectType<typeof emptySize, SizeType.Arr>('=');
* ```
*
* @see {@link length} - Alias for this function
* @see {@link isEmpty} for checking if size is 0
* @see {@link isNonEmpty} for checking if size > 0
*/
Arr.size = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
array.length;
// type guard
/**
* Type guard that checks if a value is an array, excluding types that cannot be arrays.
* This function refines the type by filtering out non-array types from unions.
* @template E The input type that may or may not be an array.
* @param value The value to check.
* @returns `true` if the value is an array, `false` otherwise.
* @example
* ```ts
* function processValue(value: string | number[] | null) {
* if (Arr.isArray(value)) {
* // value is now typed as number[]
* console.log(value.length);
* }
* }
*
* Arr.isArray([1, 2, 3]); // true
* Arr.isArray("hello"); // false
* Arr.isArray(null); // false
* ```
*/
Arr.isArray = (value) => Array.isArray(value);
// validation
/**
* Type guard that checks if an array is empty (has no elements).
*
* This function serves as both a runtime check and a TypeScript type guard,
* narrowing the array type to `readonly []` when the check passes. It's useful
* for conditional logic and type-safe handling of potentially empty arrays.
*
* @template E The type of elements in the array.
* @param array The array to check for emptiness.
* @returns `true` if the array has length 0, `false` otherwise.
* When `true`, TypeScript narrows the type to `readonly []`.
*
* @example
* ```typescript
* // Basic emptiness checking
* const emptyArray: number[] = [];
* const nonEmptyArray = [1, 2, 3];
*
* console.log(Arr.isEmpty(emptyArray)); // true
* console.log(Arr.isEmpty(nonEmptyArray)); // false
*
* // Type guard behavior
* function processArray(arr: readonly number[]) {
* if (Arr.isEmpty(arr)) {
* // arr is now typed as readonly []
* console.log('Array is empty');
* // arr[0]; // type error!
* return 0;
* }
* }
*
* // Early returns
* function sumArray(numbers: readonly number[]): number {
* if (Arr.isEmpty(numbers)) {
* return 0; // Handle empty case early
* }
* return numbers.reduce((sum, n) => sum + n, 0);
* }
*
* ```
*
* @see {@link isNonEmpty} for the opposite check (non-empty arrays)
* @see {@link size} for getting the exact length
* @see {@link isArrayOfLength} for checking specific lengths
*/
Arr.isEmpty = (array) => array.length === 0;
/**
* Type guard that checks if an array is non-empty (has at least one element).
*
* This function serves as both a runtime check and a TypeScript type guard,
* narrowing the array type to `NonEmptyArray<E>` when the check passes. This enables
* safe access to array elements without undefined checks, as TypeScript knows the array
* has at least one element.
*
* @template E The type of elements in the array.
* @param array The array to check for non-emptiness.
* @returns `true` if the array has length > 0, `false` otherwise.
* When `true`, TypeScript narrows the type to `NonEmptyArray<E>`.
*
* @example
* ```typescript
* // Basic non-emptiness checking
* const emptyArray: number[] = [];
* const nonEmptyArray = [1, 2, 3];
*
* console.log(Arr.isNonEmpty(emptyArray)); // false
* console.log(Arr.isNonEmpty(nonEmptyArray)); // true
*
* // Type guard behavior enables safe element access
* function getFirstElement(arr: readonly number[]): number | undefined {
* if (Arr.isNonEmpty(arr)) {
* // arr is now typed as NonEmptyArray<number>
* return arr[0]; // Safe - no undefined, TypeScript knows this exists
* }
* return undefined;
* }
*
* // Safe operations on non-empty arrays
* function processData(data: readonly string[]) {
* if (!Arr.isNonEmpty(data)) return; // early return pattern
*
* // This is now safe without undefined checks
* const first = data[0];
*
* // Can safely use non-empty array methods
* const lastElement = Arr.last(data);
*
* }
*
* // Filtering and working with arrays
* const possiblyEmptyArrays: readonly number[][] = [
* [1, 2, 3],
* [],
* [4, 5],
* []
* ];
*
* // Get only non-empty arrays with proper typing
* const definitelyNonEmpty = possiblyEmptyArrays.filter(Arr.isNonEmpty);
* // Type: NonEmptyArray<number>[]
*
* // Now safe to access elements
* const firstElements = definitelyNonEmpty.map(arr => arr[0]); // [1, 4]
*
* // Early validation
* function calculateAverage(numbers: readonly number[]): number {
* if (!Arr.isNonEmpty(numbers)) {
* throw new Error('Cannot calculate average of empty array');
* }
*
* // numbers is now NonEmptyArray<number>
* return Arr.sum(numbers) / Arr.size(numbers);
* }
*
* // Functional composition
* const arrayGroups = [
* [1, 2],
* [],
* [3, 4, 5],
* []
* ];
*
* const nonEmptyGroups = arrayGroups
* .filter(Arr.isNonEmpty) // Filter to NonEmptyArray<number>[]
* .map(group => group[0]); // Safe access to first element: [1, 3]
*
* // Combined with other array operations
* function processArraySafely<T>(
* arr: readonly T[],
* processor: (item: T) => string
* ): string {
* if (Arr.isNonEmpty(arr)) {
* return arr.map(processor).join(' -> ');
* }
* return 'No items to process';
* }
*
* // Type inference examples
* const testArray = [1, 2, 3];
* const isNonEmptyResult = Arr.isNonEmpty(testArray);
*
* expectType<typeof isNonEmptyResult, boolean>('=');
* expectType<Parameters<typeof Arr.isNonEmpty>[0], readonly unknown[]>('=');
*
* // Type narrowing in conditional
* if (Arr.isNonEmpty(testArray)) {
* expectType<typeof testArray, NonEmptyArray<number>>('=');
* }
* ```
*
* @see {@link isEmpty} for the opposite check (empty arrays)
* @see {@link size} for getting the exact length
* @see {@link head} for safely getting the first element
* @see {@link last} for safely getting the last element
*/
Arr.isNonEmpty = (array) => array.length > 0;
/**
* Checks if an array has a specific length.
* @template E The type of elements in the array.
* @template N The expected length of the array (must be a number type).
* @param array The array to check.
* @param len The expected length.
* @returns `true` if the array has the specified length, `false` otherwise.
* @example
* ```ts
* const arr: readonly number[] = [1, 2, 3];
* if (Arr.isArrayOfLength(arr, 3)) {
* // arr is now typed as readonly [number, number, number]
* }
* Arr.isArrayOfLength([1, 2], 3); // false
* ```
*/
Arr.isArrayOfLength = (array, len) => array.length === len;
/**
* Checks if an array has at least a specific length.
* @template E The type of elements in the array.
* @template N The minimum expected length of the array (must be a number type).
* @param array The array to check.
* @param len The minimum expected length.
* @returns `true` if the array has at least the specified length, `false` otherwise.
* @example
* ```ts
* const arr: readonly number[] = [1, 2, 3];
* if (Arr.isArrayAtLeastLength(arr, 2)) {
* // arr is now typed as readonly [number, number, ...number[]]
* }
* Arr.isArrayAtLeastLength([1], 2); // false
* ```
*/
Arr.isArrayAtLeastLength = (array, len) => array.length >= len;
/**
* Checks if an index is within the valid range of an array (i.e., `0 <= index < array.length`).
* @template E The type of elements in the array.
* @param array The input array.
* @param index The index to check.
* @returns `true` if the index is within the array bounds, `false` otherwise.
* @example
* ```ts
* Arr.indexIsInRange([10, 20], 0); // true
* Arr.indexIsInRange([10, 20], 1); // true
* Arr.indexIsInRange([10, 20], 2); // false
* Arr.indexIsInRange([10, 20], -1); // false
* Arr.indexIsInRange([], 0); // false
* ```
*/
Arr.indexIsInRange = (array, index) => Num.isInRange(0, array.length)(index);
// array creation
/**
* Creates an array of zeros with the specified length.
*
* This function provides compile-time type safety with precise return types:
* - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple with exact length
* - When `len` is a positive runtime value, returns a `NonEmptyArray<0>`
* - Otherwise, returns a `readonly 0[]` that may be empty
*
* @template N The type of the length parameter. When a `SmallUint` literal is provided,
* the return type will be a tuple of exactly that length filled with zeros.
* @param len The length of the array to create. Must be a non-negative integer.
* @returns An immutable array of zeros. The exact return type depends on the input:
* - `ArrayOfLength<N, 0>` when `N` is a `SmallUint` literal
* - `NonEmptyArray<0>` when `len` is a positive runtime value
* - `readonly 0[]` for general non-negative values
*
* @example
* ```typescript
* // Compile-time known lengths produce precise tuple types
* const exactLength = Arr.zeros(3); // readonly [0, 0, 0]
* const empty = Arr.zeros(0); // readonly []
*
* // Runtime positive values produce non-empty arrays
* const count = Math.floor(Math.random() * 5) + 1;
* const nonEmpty = Arr.zeros(count); // NonEmptyArray<0>
*
* // General runtime values may be empty
* const maybeEmpty = Arr.zeros(Math.floor(Math.random() * 5)); // readonly 0[]
*
* // Type inference examples
* expectType<typeof exactLength, readonly [0, 0, 0]>('=');
* expectType<typeof empty, readonly []>('=');
* expectType<typeof nonEmpty, NonEmptyArray<0>>('=');
* expectType<typeof maybeEmpty, readonly 0[]>('=');
* ```
*/
Arr.zeros = (len) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Array.from({ length: len }).fill(0);
/**
* Creates a sequence of consecutive integers from 0 to `len-1`.
*
* This function generates index sequences with precise compile-time typing:
* - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of consecutive integers
* - When `len` is a positive runtime value, returns a `NonEmptyArray<SizeType.Arr>`
* - Otherwise, returns a `readonly SizeType.Arr[]` that may be empty
*
* @template N The type of the length parameter. When a `SmallUint` literal is provided,
* the return type will be a tuple containing the sequence [0, 1, 2, ..., N-1].
* @param len The length of the sequence to create. Must be a non-negative integer.
* @returns An immutable array containing the sequence [0, 1, 2, ..., len-1].
* The exact return type depends on the input:
* - `Seq<N>` (precise tuple) when `N` is a `SmallUint` literal
* - `NonEmptyArray<SizeType.Arr>` when `len` is a positive runtime value
* - `readonly SizeType.Arr[]` for general non-negative values
*
* @example
* ```typescript
* // Compile-time known lengths produce precise tuple types
* const indices = Arr.seq(4); // readonly [0, 1, 2, 3]
* const empty = Arr.seq(0); // readonly []
* const single = Arr.seq(1); // readonly [0]
*
* // Runtime positive values produce non-empty arrays
* const count = Math.floor(Math.random() * 5) + 1;
* const nonEmpty = Arr.seq(count); // NonEmptyArray<SizeType.Arr>
*
* // General runtime values may be empty
* const maybeEmpty = Arr.seq(Math.floor(Math.random() * 5)); // readonly SizeType.Arr[]
*
* // Useful for generating array indices
* const data = ['a', 'b', 'c', 'd'];
* const indexSequence = Arr.seq(data.length); // [0, 1, 2, 3]
*
* // Type inference examples
* expectType<typeof indices, readonly [0, 1, 2, 3]>('=');
* expectType<typeof empty, readonly []>('=');
* expectType<typeof single, readonly [0]>('=');
* ```
*/
Arr.seq = (len) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Array.from({ length: len }, (_, i) => i);
/**
* Creates a new array of the specified length, with each position filled with the provided initial value.
*
* This function provides compile-time type safety with precise return types and performs shallow copying
* of the initial value (the same reference is used for all positions):
* - When `len` is a compile-time known `SmallUint` (0-100), returns a tuple of exactly that length
* - When `len` is a positive runtime value, returns a `NonEmptyArray<V>`
* - Otherwise, returns a `readonly V[]` that may be empty
*
* @template V The type of the initial value. The `const` constraint preserves literal types.
* @template N The type of the length parameter when it's a `SmallUint` literal.
* @param len The length of the array to create. Must be a non-negative integer.
* @param init The value to fill each position with. The same reference is used for all positions.
* @returns An immutable array filled with the initial value. The exact return type depends on the length:
* - `ArrayOfLength<N, V>` when `len` is a `SmallUint` literal
* - `NonEmptyArray<V>` when `len` is a positive runtime value
* - `readonly V[]` for general non-negative values
*
* @example
* ```typescript
* // Compile-time known lengths produce precise tuple types
* const strings = Arr.create(3, 'hello'); // readonly ['hello', 'hello', 'hello']
* const numbers = Arr.create(2, 42); // readonly [42, 42]
* const empty = Arr.create(0, 'unused'); // readonly []
*
* // Object references are shared (shallow copy behavior)
* const obj = { id: 1, name: 'test' };
* const objects = Arr.create(3, obj); // readonly [obj, obj, obj]
* objects[0] === objects[1]; // true - same reference
* objects[0].id = 999; // Mutates the shared object
* console.log(objects[1].id); // 999 - all positions affected
*
* // Runtime positive values produce non-empty arrays
* const count = Math.floor(Math.random() * 5) + 1;
* const nonEmpty = Arr.create(count, 'item'); // NonEmptyArray<string>
*
* // Literal type preservation with const assertion
* const literals = Arr.create(2, 'success' as const); // readonly ['success', 'success']
*
* // Type inference examples
* expectType<typeof strings, readonly ['hello', 'hello', 'hello']>('=');
* expectType<typeof numbers, readonly [42, 42]>('=');
* expectType<typeof empty, readonly []>('=');
* ```
*
* @see {@link zeros} for creating arrays filled with zeros
* @see {@link seq} for creating sequences of consecutive integers
*/
Arr.create = (len, init) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Array.from({ length: Math.max(0, len) }, () => init);
/**
* Creates an array from a generator function.
*
* This utility function provides enhanced type safety by constraining the generator function
* to prevent incorrect return values. The generator can only yield values of type T and
* must return void, which helps catch common mistakes like returning values instead of yielding.
*
* @template T - The type of elements in the generated array
* @param generatorFn - A function that returns a generator yielding elements of type T
* @returns A readonly array containing all yielded values from the generator
*
* @example
* ```typescript
* const nums:readonly number[] = Arr.generate<number>(function* () {
* yield 1;
* yield* [2, 3];
* });
*
* assert.deepStrictEqual(nums, [1, 2, 3]);
*
* // Type safety - prevents incorrect returns:
* const nums2 = Arr.generate<number>(function* () {
* yield 1;
* if (someCondition) {
* return; // OK - returning is allowed, but must be void
* }
* yield* [2, 3];
* // return 1; // NG - TypeScript error, cannot return T
* });
* ```
*/
Arr.generate = (generatorFn) => Array.from(generatorFn());
/**
* Creates a shallow copy of an array, preserving the exact type signature.
*
* This function creates a new array with the same elements as the input, but with a new array reference.
* Object references within the array are preserved (shallow copy), and the readonly/mutable status
* of the array type is maintained.
*
* @template Ar The exact type of the input array, preserving tuple types, readonly status, and element types.
* @param array The array to copy. Can be any array type: mutable, readonly, tuple, or general array.
* @returns A new array that is a shallow copy of the input. The return type exactly matches the input type,
* preserving readonly status, tuple structure, and element types.
*
* @example
* ```typescript
* // Mutable arrays remain mutable
* const mutableOriginal = [1, 2, 3];
* const mutableCopy = Arr.copy(mutableOriginal); // number[]
* mutableCopy[0] = 999; // OK - still mutable
* mutableOriginal[0]; // 1 - original unchanged
*
* // Readonly arrays remain readonly
* const readonlyOriginal = [1, 2, 3] as const;
* const readonlyCopy = Arr.copy(readonlyOriginal); // readonly [1, 2, 3]
* // readonlyCopy[0] = 999; // Error - readonly array
*
* // Tuple types are preserved
* const tupleOriginal: [string, number, boolean] = ['hello', 42, true];
* const tupleCopy = Arr.copy(tupleOriginal); // [string, number, boolean]
*
* // Shallow copy behavior with objects
* const objectArray = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
* const objectCopy = Arr.copy(objectArray);
* objectCopy[0].name = 'Charlie'; // Mutates the shared object reference
* console.log(objectArray[0].name); // 'Charlie' - original affected
* objectCopy.push({ id: 3, name: 'Dave' }); // Array structure changes don't affect original
* console.log(objectArray.length); // 2 - original array length unchanged
*
* // Empty arrays
* const emptyArray: number[] = [];
* const emptyCopy = Arr.copy(emptyArray); // number[]
* const emptyTuple = [] as const;
* const emptyTupleCopy = Arr.copy(emptyTuple); // readonly []
*
* // Type inference examples
* expectType<typeof mutableCopy, number[]>('=');
* expectType<typeof readonlyCopy, readonly [1, 2, 3]>('=');
* expectType<typeof tupleCopy, [string, number, boolean]>('=');
* ```
*
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice | Array.prototype.slice}
* The underlying implementation uses `slice()` for efficient shallow copying
*/
Arr.copy = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
array.slice();
function range$1(start, end, step = 1) {
return Array.from(range(start, end, step));
}
Arr.range = range$1;
function at(...args) {
switch (args.length) {
case 2: {
const [array, index] = args;
return pipe(index < 0 ? array.length + index : index).map((normalizedIndex) => normalizedIndex < 0 || normalizedIndex >= array.length
? Optional.none
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Optional.some(array[normalizedIndex])).value;
}
case 1: {
const [index] = args;
return (array) => at(array, index);
}
}
}
Arr.at = at;
/**
* Returns the first element of an array wrapped in an Optional.
*
* This function provides type-safe access to the first element with precise return types:
* - For empty arrays: returns `Optional.None`
* - For tuples with known first element: returns `Optional.Some<FirstElementType>`
* - For non-empty arrays: returns `Optional.Some<ElementType>`
* - For general arrays: returns `Optional<ElementType>`
*
* The function leverages TypeScript's type system to provide the most precise return type
* based on the input array type, making it safer than direct indexing.
*
* @template E The type of elements in the array.
* @param array The array to get the first element from.
* @returns An Optional containing the first element:
* - `Optional.None` if the array is empty
* - `Optional.Some<E>` containing the first element if the array is non-empty
*
* @example
* ```typescript
* // Empty array - precise None type
* const emptyResult = Arr.head([]); // Optional.None
* console.log(Optional.isNone(emptyResult)); // true
*
* // Tuple with known structure - precise Some type
* const tupleResult = Arr.head(['first', 'second', 'third'] as const);
* // Type: Optional.Some<'first'>
* if (Optional.isSome(tupleResult)) {
* console.log(tupleResult.value); // 'first' - TypeScript knows exact type
* }
*
* // Non-empty array - guaranteed Some type
* const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>;
* const guaranteedResult = Arr.head(nonEmpty); // Optional.Some<number>
* // No need to check - always Some for NonEmptyArray
*
* // General array - may be Some or None
* const generalArray: number[] = [1, 2, 3];
* const maybeResult = Arr.head(generalArray); // Optional<number>
* if (Optional.isSome(maybeResult)) {
* console.log(`First element: ${maybeResult.value}`);
* } else {
* console.log('Array is empty');
* }
*
* // Working with different types
* const strings = ['hello', 'world'];
* const firstString = Arr.head(strings); // Optional<string>
*
* const objects = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
* const firstObject = Arr.head(objects); // Optional<{id: number, name: string}>
*
* // Functional composition
* const getFirstElements = (arrays: readonly number[][]) =>
* arrays.map(Arr.head).filter(Optional.isSome);
*
* const nestedArrays = [[1, 2], [3, 4], [], [5]];
* const firstElements = getFirstElements(nestedArrays);
* // [Optional.Some(1), Optional.Some(3), Optional.Some(5)]
*
* // Type inference examples
* expectType<typeof emptyResult, Optional.None>('=');
* expectType<typeof tupleResult, Optional.Some<'first'>>('=');
* expectType<typeof guaranteedResult, Optional.Some<number>>('=');
* expectType<typeof maybeResult, Optional<number>>('=');
* ```
*
* @see {@link last} for getting the last element
* @see {@link at} for accessing elements at specific indices
* @see {@link tail} for getting all elements except the first
*/
Arr.head = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(array.length === 0 ? Optional.none : Optional.some(array.at(0)));
/**
* Returns the last element of an array wrapped in an Optional.
*
* This function provides type-safe access to the last element with precise return types:
* - For empty arrays: returns `Optional.None`
* - For tuples with known last element: returns `Optional.Some<LastElementType>`
* - For non-empty arrays: returns `Optional.Some<ElementType>`
* - For general arrays: returns `Optional<ElementType>`
*
* The function leverages TypeScript's type system to provide the most precise return type
* based on the input array type, making it safer than direct indexing.
*
* @template E The type of elements in the array.
* @param array The array to get the last element from.
* @returns An Optional containing the last element:
* - `Optional.None` if the array is empty
* - `Optional.Some<E>` containing the last element if the array is non-empty
*
* @example
* ```typescript
* // Empty array - precise None type
* const emptyResult = Arr.last([]); // Optional.None
* console.log(Optional.isNone(emptyResult)); // true
*
* // Tuple with known structure - precise Some type
* const tupleResult = Arr.last(['first', 'middle', 'last'] as const);
* // Type: Optional.Some<'last'>
* if (Optional.isSome(tupleResult)) {
* console.log(tupleResult.value); // 'last' - TypeScript knows exact type
* }
*
* // Non-empty array - guaranteed Some type
* const nonEmpty: NonEmptyArray<number> = [10, 20, 30] as NonEmptyArray<number>;
* const guaranteedResult = Arr.last(nonEmpty); // Optional.Some<number>
* // No need to check - always Some for NonEmptyArray
*
* // General array - may be Some or None
* const generalArray: number[] = [1, 2, 3];
* const maybeResult = Arr.last(generalArray); // Optional<number>
* if (Optional.isSome(maybeResult)) {
* console.log(`Last element: ${maybeResult.value}`);
* } else {
* console.log('Array is empty');
* }
*
* // Working with different types
* const strings = ['hello', 'world', 'example'];
* const lastString = Arr.last(strings); // Optional<string>
*
* const coordinates = [{x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2}];
* const lastCoordinate = Arr.last(coordinates); // Optional<{x: number, y: number}>
*
* // Single element arrays
* const single = [42];
* const singleResult = Arr.last(single); // Optional<number> containing 42
*
* // Functional composition with arrays of arrays
* const getLastElements = (arrays: readonly string[][]) =>
* arrays.map(Arr.last).filter(Optional.isSome);
*
* const nestedArrays = [['a', 'b'], ['c'], [], ['d', 'e', 'f']];
* const lastElements = getLastElements(nestedArrays);
* // [Optional.Some('b'), Optional.Some('c'), Optional.Some('f')]
*
* // Common pattern: get last element or default
* const data = [10, 20, 30];
* const lastOrDefault = Optional.unwrapOr(Arr.last(data), 0); // 30
* const emptyLastOrDefault = Optional.unwrapOr(Arr.last([]), 0); // 0
*
* // Type inference examples
* expectType<typeof emptyResult, Optional.None>('=');
* expectType<typeof tupleResult, Optional.Some<'last'>>('=');
* expectType<typeof guaranteedResult, Optional.Some<number>>('=');
* expectType<typeof maybeResult, Optional<number>>('=');
* ```
*
* @see {@link head} for getting the first element
* @see {@link at} for accessing elements at specific indices with negative indexing support
* @see {@link butLast} for getting all elements except the last
*/
Arr.last = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(array.length === 0 ? Optional.none : Optional.some(array.at(-1)));
function sliceClamped(...args) {
switch (args.length) {
case 3: {
const [array, start, end] = args;
const startClamped = Num.clamp(0, array.length)(start);
// Ensure endClamped is not less than startClamped.
const endClamped = Num.clamp(startClamped, array.length)(end);
return array.slice(startClamped, endClamped);
}
case 2: {
const [start, end] = args;
return (array) => sliceClamped(array, start, end);
}
}
}
Arr.sliceClamped = sliceClamped;
/**
* Returns all elements of an array except the first one.
* @template E The type of the array (can be a tuple for more precise typing).
* @param array The input array.
* @returns A new array containing all elements except the first. The type is inferred as `List.Tail<T>`.
* @example
* ```ts
* Arr.tail([1, 2, 3] as const); // [2, 3]
* Arr.tail([1] as const); // []
* Arr.tail([]); // []
* ```
*/
Arr.tail = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
array.slice(1);
/**
* Returns all elements of an array except the last one.
* @template E The type of the array (can be a tuple for more precise typing).
* @param array The input array.
* @returns A new array containing all elements except the last. The type is inferred as `List.ButLast<T>`.
* @example
* ```ts
* Arr.butLast([1, 2, 3] as const); // [1, 2]
* Arr.butLast([1] as const); // []
* Arr.butLast([]); // []
* ```
*/
Arr.butLast = (array) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(Arr.isEmpty(array) ? [] : array.slice(0, -1));
function take(...args) {
switch (args.length) {
case 2: {
const [array, num] = args;
return sliceClamped(array, 0, num);
}
case 1: {
const [num] = args;
return (array) => take(array, num);
}
}
}
Arr.take = take;
function takeLast(...args) {
switch (args.length) {
case 2: {
const [array, num] = args;
return sliceClamped(array, Uint32.sub(Arr.size(array), num), Arr.size(array));
}
case 1: {
const [num] = args;
return (array) => takeLast(array, num);
}
}
}
Arr.takeLast = takeLast;
function skip(...args) {
switch (args.length) {
case 2: {
const [array, num] = args;
return sliceClamped(array, num, Arr.size(array));
}
case 1: {
const [num] = args;
return (array) => skip(array, num);
}
}
}
Arr.skip = skip;
function skipLast(...args) {
switch (args.length) {
case 2: {
const [array, num] = args;
return sliceClamped(array, 0, Uint32.sub(Arr.size(array), num));
}
case 1: {
const [num] = args;
return (array) => skipLast(array, num);
}
}
}
Arr.skipLast = skipLast;
function set(...args) {
switch (args.length) {
case 3: {
const [array, index, newValue] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return array.with(index, newValue);
}
case 2: {
const [index, newValue] = args;
return (array) => set(array, index, newValue);
}
}
}
Arr.set = set;
function toUpdated(...args) {
switch (args.length) {
case 3: {
const [array, index, updater] = args;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-type-assertion
return array.with(index, updater(array[index]));
}
case 2: {
const [index, updater] = args;
return (array) => toUpdated(array, index, updater);
}
}
}
Arr.toUpdated = toUpdated;
function toInserted(...args) {
switch (args.length) {
case 3: {
const [array, index, newValue] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return array.toSpliced(index, 0, newValue);
}
case 2: {
const [index, newValue] = args;
return (array) => toInserted(array, index, newValue);
}
}
}
Arr.toInserted = toInserted;
function toRemoved(...args) {
switch (args.length) {
case 2: {
const [array, index] = args;
return array.toSpliced(index, 1);
}
case 1: {
const [index] = args;
return (array) => toRemoved(array, index);
}
}
}
Arr.toRemoved = toRemoved;
function toPushed(...args) {
switch (args.length) {
case 2: {
const [array, newValue] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return array.toSpliced(array.length, 0, newValue);
}
case 1: {
const [newValue] = args;
return (array) => toPushed(array, newValue);
}
}
}
Arr.toPushed = toPushed;
function toUnshifted(...args) {
switch (args.length) {
case 2: {
const [array, newValue] = args;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return array.toSpliced(0, 0, newValue);
}
case 1: {
const [newValue] = args;
return (array) => toUnshifted(array, newValue);
}
}
}
Arr.toUnshifted = toUnshifted;
function toFilled(...args) {
switch (args.length) {
case 2: {
const [array, value] = args;
return Arr.create(asPositiveUint32(array.length), value);
}
case 1: {
const [value] = args;
return (array) => toFilled(array, value);
}
}
}
Arr.toFilled = toFilled;
function toRangeFilled(...args) {
switch (args.length) {
case 3: {
const [array, value, [start, end]] = args;
const mut_cp = castMutable(Arr.copy(array));
mut_cp.fill(value, start, end);
return mut_cp;
}
case 2: {
const [value, fillRange] = args;
return (array) => toRangeFilled(array, value, fillRange);
}
}
}
Arr.toRangeFilled = toRangeFilled;
function find(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
const foundIndex = array.findIndex(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
predicate);
return foundIndex === -1
? Optional.none
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Optional.some(array[foundIndex]);
}
case 1: {
const [predicate] = args;
return (array) => find(array, predicate);
}
}
}
Arr.find = find;
function findLast(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
const foundIndex = array.findLastIndex(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
predicate);
return foundIndex === -1
? Optional.none
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Optional.some(array[foundIndex]);
}
case 1: {
const [predicate] = args;
return (array) => findLast(array, predicate);
}
}
}
Arr.findLast = findLast;
function findIndex(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
return pipe(array.findIndex(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
predicate)).map((idx) => (idx >= 0 ? asUint32(idx) : -1)).value;
}
case 1: {
const [predicate] = args;
return (array) => findIndex(array, predicate);
}
}
}
Arr.findIndex = findIndex;
function findLastIndex(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
return pipe(array.findLastIndex(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
predicate)).map((idx) => (idx >= 0 ? asUint32(idx) : -1)).value;
}
case 1: {
const [predicate] = args;
return (array) => findLastIndex(array, predicate);
}
}
}
Arr.findLastIndex = findLastIndex;
function indexOf(...args) {
switch (args.length) {
case 2: {
const [array, searchElement] = args;
const index = array.indexOf(searchElement);
return index !== -1 ? asUint32(index) : -1;
}
case 1: {
const [searchElement] = args;
return (array) => indexOf(array, searchElement);
}
}
}
Arr.indexOf = indexOf;
function indexOfFrom(...args) {
switch (args.length) {
case 3: {
const [array, searchElement, fromIndex] = args;
const index = array.indexOf(searchElement, fromIndex);
return index !== -1 ? asUint32(index) : -1;
}
case 2: {
const [searchElement, fromIndex] = args;
return (array) => indexOfFrom(array, searchElement, fromIndex);
}
}
}
Arr.indexOfFrom = indexOfFrom;
function lastIndexOf(...args) {
switch (args.length) {
case 2: {
const [array, searchElement] = args;
const index = array.lastIndexOf(searchElement);
return index !== -1 ? asUint32(index) : -1;
}
case 1: {
const [searchElement] = args;
return (array) => lastIndexOf(array, searchElement);
}
}
}
Arr.lastIndexOf = lastIndexOf;
function lastIndexOfFrom(...args) {
switch (args.length) {
case 3: {
const [array, searchElement, fromIndex] = args;
const index = array.lastIndexOf(searchElement, fromIndex);
return index !== -1 ? asUint32(index) : -1;
}
case 2: {
const [searchElement, fromIndex] = args;
return (array) => lastIndexOfFrom(array, searchElement, fromIndex);
}
}
}
Arr.lastIndexOfFrom = lastIndexOfFrom;
function every(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
return array.every((a, i) => predicate(a, asUint32(i)));
}
case 1: {
const [predicate] = args;
return (array) => every(array, predicate);
}
}
}
Arr.every = every;
function some(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
return array.some((a, i) => predicate(a, asUint32(i)));
}
case 1: {
const [predicate] = args;
return (array) => some(array, predicate);
}
}
}
Arr.some = some;
function foldl(...args) {
switch (args.length) {
case 3: {
const [array, callbackfn, initialValue] = args;
return array.reduce((prev, curr, index) => callbackfn(prev, curr, asUint32(index)), initialValue);
}
case 2: {
const [callbackfn, initialValue] = args;
return (array) => foldl(array, callbackfn, initialValue);
}
}
}
Arr.foldl = foldl;
function foldr(...args) {
switch (args.length) {
case 3: {
const [array, callbackfn, initialValue] = args;
return array.reduceRight((prev, curr, index) => callbackfn(prev, curr, asUint32(index)), initialValue);
}
case 2: {
const [callbackfn, initialValue] = args;
return (array) => foldr(array, callbackfn, initialValue);
}
}
}
Arr.foldr = foldr;
function min(array, comparator) {
if (!Arr.isNonEmpty(array)) {
return Optional.none;
}
const cmp = comparator ?? ((x, y) => Num.from(x) - Num.from(y));
return Optional.some(array.reduce((currentMin, curr) => (cmp(curr, currentMin) < 0 ? curr : currentMin), array[0]));
}
Arr.min = min;
function max(array, comparator) {
const cmp = comparator ?? ((x, y) => Num.from(x) - Num.from(y));
// Find max by finding min with an inverted comparator
return min(array, (x, y) => -cmp(x, y));
}
Arr.max = max;
function minBy(array, comparatorValueMapper, comparator) {
return min(array, (x, y) => comparator === undefined
? Num.from(comparatorValueMapper(x)) -
Num.from(comparatorValueMapper(y))
: comparator(comparatorValueMapper(x), comparatorValueMapper(y)));
}
Arr.minBy = minBy;
function maxBy(array, comparatorValueMapper, comparator) {
return max(array, (x, y) => comparator === undefined
? Num.from(comparatorValueMapper(x)) -
Num.from(comparatorValueMapper(y))
: comparator(comparatorValueMapper(x), comparatorValueMapper(y)));
}
Arr.maxBy = maxBy;
function count(...args) {
switch (args.length) {
case 2: {
const [array, predicate] = args;
return array.reduce((acc, curr, index) => predicate(curr, asUint32(index)) ? Uint32.add(acc, 1) : acc, asUint32(0));
}
case 1: {
const [predicate] = args;
return (array) => count(array, predicate);
}
}
}
Arr.count = count;
function countBy(...args) {
switch (args.length) {
case 2: {
const [array, grouper] = args;
const mut_groups = new Map();
for (const [index, e] of array.entries()) {
const key = grouper(e, asUint32(index));
const curr = mut_groups.get(key) ?? 0;
mut_groups.set(key, asUint32(curr + 1));
}
return IMap.create(mut_groups);
}
case 1: {
const [grouper] = args;
return (array) => countBy(array, grouper);
}
}
}
Arr.countBy = countBy;
function sum(array) {
return array.reduce((prev, curr) => prev + curr, 0);
}
Arr.sum = sum;
function join(...args) {
switch (args.length) {
case 0:
return (array) => joinImpl(array, undefined);
case 1: {
const [arg] = args;