# @helpers4/array

> Tree-shakable TypeScript utility functions for the `array` domain.
> Package: `@helpers4/array` — Version: 2.0.1
> License: LGPL-3.0-or-later

## Installation

```sh
npm install @helpers4/array
# or
pnpm add @helpers4/array
```

## Usage

```typescript
import { cartesianProduct, chunk, compact, ... } from '@helpers4/array';
```

## Functions

| Function | Description |
|---|---|
| `cartesianProduct` | Computes the Cartesian product of the provided arrays.  Returns all possible tuples formed by pickin |
| `chunk` | Chunks an array into smaller arrays of specified size |
| `compact` | Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. |
| `countBy` | Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key |
| `createSortByDateFn` | Creates a sort function for objects by date property |
| `createSortByNumberFn` | Creates a sort function for objects by number property |
| `createSortByStringFn` | Creates a sort function for objects by string property |
| `difference` | Returns the difference between two arrays (items in first array but not in second) |
| `ensureArray` | Wraps a value in an array if it is not already one. If the value is already an array, it is returned |
| `equalsDeep` | Recursive structural array equality.  Two arrays are equal when they have the same length and each p |
| `equalsShallow` | Positional, one-level (shallow) array equality.  Two arrays are equal when they have the same length |
| `equalsUnordered` | Order-independent (set-style) array equality.  Two arrays are considered equal when they have the sa |
| `intersection` | Compute the intersection of two arrays, meaning the elements that are present in both arrays. |
| `intersects` | Simple helper that check if two lists shared at least an item in common. |
| `partition` | Splits an array into two groups based on a predicate function. The first group contains elements for |
| `range` | Generates an array of sequential numbers from start to end (exclusive). If only one argument is prov |
| `sample` | Picks one or more random elements from an array. When called without a count, returns a single eleme |
| `shuffle` | Randomly reorders elements of an array using the Fisher-Yates algorithm. Returns a new array without |
| `sortNumberAscFn` | Sort numbers in ascending order |
| `sortNumberDescFn` | Sort numbers in descending order |
| `sortStringAscFn` | Sort strings in ascending order |
| `sortStringAscInsensitiveFn` | Sort strings in ascending order (case insensitive) |
| `sortStringDescFn` | Sort strings in descending order |
| `unique` | Removes duplicate values from an array |
| `unzip` | Splits an array of tuples into separate arrays, one per position.  The inverse of zip. |
| `without` | Returns a new array with all occurrences of the given values removed.  Unlike `difference`, which op |
| `zip` | Combines multiple arrays element-by-element into an array of tuples. The result length equals the le |

---

## API Reference

### `cartesianProduct`

Computes the Cartesian product of the provided arrays.

Returns all possible tuples formed by picking one element from each input array,
in lexicographic order relative to the input order.

```typescript
import { cartesianProduct } from '@helpers4/array';

cartesianProduct<T extends readonly readonly unknown[][]>(arrays: T): mapped[]
```

**Parameters:**

- `arrays: T` — Two or more arrays to combine.

**Returns:** `mapped[]` — An array of tuples, each containing one element from each input array.

**Examples:**

*Combine two arrays*

Returns all ordered pairs from two arrays.

```typescript
cartesianProduct([1, 2], ['a', 'b'])
// => [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
```

*Generate product combinations*

Useful for generating all size/color variant combinations.

```typescript
cartesianProduct(['S', 'M', 'L'], ['red', 'blue'])
// => [['S','red'],['S','blue'],['M','red'],['M','blue'],['L','red'],['L','blue']]
```

*Empty input returns empty array*

If any input array is empty, the result is an empty array.

```typescript
cartesianProduct([1, 2], []) // => []
```

---

### `chunk`

Chunks an array into smaller arrays of specified size

```typescript
import { chunk } from '@helpers4/array';

chunk<T>(array: T[], size: number): T[][]
```

**Parameters:**

- `array: T[]` — The array to chunk
- `size: number` — The size of each chunk

**Returns:** `T[][]` — Array of chunks

**Examples:**

*Split an array into pairs*

Chunks an array of 5 elements into groups of 2, with the last chunk containing the remainder.

```typescript
chunk([1, 2, 3, 4, 5], 2)
// => [[1, 2], [3, 4], [5]]
```

*Handle exact divisions*

When the array length is evenly divisible by the chunk size, all chunks are equal.

```typescript
chunk([1, 2, 3, 4], 2)
// => [[1, 2], [3, 4]]
```

*Return empty array for invalid size*

A size of 0 or negative returns an empty array.

```typescript
chunk([1, 2, 3], 0)
// => []
```

---

### `compact`

Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array.

```typescript
import { compact } from '@helpers4/array';

compact<T>(array: readonly Falsy | T[]): Exclude<T, Falsy>[]
```

**Parameters:**

- `array: readonly Falsy | T[]` — The array to compact

**Returns:** `Exclude<T, Falsy>[]` — A new array with only truthy values

**Examples:**

*Remove falsy values*

Removes all falsy values (false, null, undefined, 0, "", NaN) from an array.

```typescript
compact([0, 1, false, 2, '', 3, null, undefined, NaN])
// => [1, 2, 3]
```

*Filter nullable strings*

Useful to clean up arrays with null/undefined gaps.

```typescript
compact(['hello', null, 'world', undefined, ''])
// => ['hello', 'world']
```

---

### `countBy`

Groups the elements of an array by the key returned by `keyFn` and returns a
record mapping each key to the number of matching elements.

```typescript
import { countBy } from '@helpers4/array';

countBy<T, K extends PropertyKey>(array: readonly T[], keyFn: function): Partial<Record<K, number>>
```

**Parameters:**

- `array: readonly T[]` — The array to count.
- `keyFn: function` — A function that returns the grouping key for each element.

**Returns:** `Partial<Record<K, number>>` — A `Partial<Record<K, number>>` where each key maps to its element count.

**Examples:**

*Count by parity*

Groups items by the string key returned by the callback and counts occurrences.

```typescript
countBy([1, 2, 3, 4, 5], n => n % 2 === 0 ? 'even' : 'odd')
// => { odd: 3, even: 2 }
```

*Count commit types*

Use any string transform as the grouping key.

```typescript
const commits = ['feat: add x', 'fix: bug', 'feat: add y'];
countBy(commits, msg => msg.split(':')[0])
// => { feat: 2, fix: 1 }
```

---

### `createSortByDateFn`

Creates a sort function for objects by date property

```typescript
import { createSortByDateFn } from '@helpers4/array';

createSortByDateFn<T extends Record<string, unknown>>(property?: keyof T): SortFn<T>
```

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to 'date')

**Returns:** `SortFn<T>` — Sort function

---

### `createSortByNumberFn`

Creates a sort function for objects by number property

```typescript
import { createSortByNumberFn } from '@helpers4/array';

createSortByNumberFn<T extends Record<string, unknown>>(property?: keyof T): SortFn<T>
```

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to 'value')

**Returns:** `SortFn<T>` — Sort function

---

### `createSortByStringFn`

Creates a sort function for objects by string property

```typescript
import { createSortByStringFn } from '@helpers4/array';

createSortByStringFn<T extends Record<string, unknown>>(property?: keyof T, caseInsensitive: boolean): SortFn<T>
```

**Parameters:**

- `property?: keyof T` — The property to sort by (defaults to trying 'value', 'label', 'title', 'description')
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case

**Returns:** `SortFn<T>` — Sort function

---

### `difference`

Returns the difference between two arrays (items in first array but not in second)

```typescript
import { difference } from '@helpers4/array';

difference<T>(array1: T[], array2: T[]): T[]
```

**Parameters:**

- `array1: T[]` — First array
- `array2: T[]` — Second array

**Returns:** `T[]` — Array with items from first array not present in second array

**Examples:**

*Get items only in the first array*

Returns elements present in the first array but not in the second.

```typescript
difference([1, 2, 3, 4], [2, 4])
// => [1, 3]
```

---

### `ensureArray`

Wraps a value in an array if it is not already one.
If the value is already an array, it is returned as-is.
If the value is null or undefined, returns an empty array.
When a depth is specified, the resulting array is flattened
to that depth (like `Array.prototype.flat(depth)`).

```typescript
import { ensureArray } from '@helpers4/array';

ensureArray<T>(value: T | readonly T[] | null | undefined): T[]
```

**Parameters:**

- `value: T | readonly T[] | null | undefined` — The value to ensure is an array

**Returns:** `T[]` — The value wrapped in an array, or the value itself if already an array

```typescript
import { ensureArray } from '@helpers4/array';

ensureArray<T>(value: T | readonly T[] | null | undefined, depth: number): unknown[]
```

**Parameters:**

- `value: T | readonly T[] | null | undefined` — The value to ensure is an array
- `depth: number` — Depth to flatten the resulting array

**Returns:** `unknown[]` — The flattened array (element types may differ from `T` due to flattening)

**Examples:**

*Wrap a single value*

Wraps a non-array value in an array.

```typescript
ensureArray('hello')
// => ['hello']
```

*Pass through an existing array*

Returns the array as-is if already an array.

```typescript
ensureArray([1, 2, 3])
// => [1, 2, 3]
```

*Handle null and undefined*

Returns an empty array for null or undefined values.

```typescript
ensureArray(null)
// => []
```

*Flatten nested arrays with depth*

Flattens the resulting array to a given depth, like Array.prototype.flat().

```typescript
ensureArray([[1, [2, 3]], [4]], 1)
// => [1, [2, 3], 4]
```

---

### `equalsDeep`

Recursive structural array equality.

Two arrays are equal when they have the same length and each pair of
elements at the same index is structurally equal:
- Arrays recurse with `equalsDeep`.
- Plain objects recurse key-by-key with structural comparison.
- `Date` instances are compared by their epoch value.
- All other values use strict equality (`===`), which means `NaN !== NaN`
  and special objects (Map, Set, RegExp, Promise, class instances\u2026) are
  compared by reference.

For positional one-level comparison use equalsShallow. For
order-independent comparison use equalsUnordered.

```typescript
import { equalsDeep } from '@helpers4/array';

equalsDeep<T>(arrA: readonly T[], arrB: readonly T[]): boolean
```

**Parameters:**

- `arrA: readonly T[]` — First array to compare
- `arrB: readonly T[]` — Second array to compare

**Returns:** `boolean` — `true` if arrays are deeply equal, `false` otherwise.

**Examples:**

*Compare nested arrays*

Deeply compares two arrays including nested structures.

```typescript
equalsDeep([[1, 2], [3]], [[1, 2], [3]])
// => true
```

*Detect nested differences*

Returns false when nested arrays differ.

```typescript
equalsDeep([[1, 2]], [[1, 3]])
// => false
```

---

### `equalsShallow`

Positional, one-level (shallow) array equality.

Two arrays are equal when they have the same length and each pair of
elements at the same index satisfies strict equality (`===`). No
recursion: nested arrays/objects are compared by reference.

For recursive structural comparison use equalsDeep. For
order-independent comparison use equalsUnordered.

```typescript
import { equalsShallow } from '@helpers4/array';

equalsShallow<T>(arrA: readonly T[], arrB: readonly T[]): boolean
```

**Parameters:**

- `arrA: readonly T[]` — First array to compare
- `arrB: readonly T[]` — Second array to compare

**Returns:** `boolean` — `true` if every element matches by `===` at the same index, `false` otherwise.

**Examples:**

*Compare identical arrays*

Uses JSON.stringify for a fast shallow comparison.

```typescript
equalsShallow([1, 2, 3], [1, 2, 3])
// => true
```

*Detect order differences*

Unlike equals, equalsShallow is order-sensitive.

```typescript
equalsShallow([1, 2], [2, 1])
// => false
```

---

### `equalsUnordered`

Order-independent (set-style) array equality.

Two arrays are considered equal when they have the same length and every
element of `arr1` has at least one structural match in `arr2` (and vice
versa via the length check). Nested arrays are compared recursively with
the same order-independent semantics. Nested plain objects are compared
with equalsShallow from `object/`. All other values use strict
equality (`===`).

Use this when the inputs represent unordered collections (sets, tags…).
For positional equality use equalsShallow or equalsDeep
from this category.

```typescript
import { equalsUnordered } from '@helpers4/array';

equalsUnordered<T>(arr1: readonly T[], arr2: readonly T[]): boolean
```

**Parameters:**

- `arr1: readonly T[]` — First array
- `arr2: readonly T[]` — Second array

**Returns:** `boolean` — `true` if both arrays contain the same items regardless of order, `false` otherwise.

**Examples:**

*Compare identical arrays regardless of order*

Returns true when both arrays contain the same elements, in any order.

```typescript
equalsUnordered([1, 2, 3], [3, 2, 1])
// => true
```

*Detect different arrays*

Returns false when arrays contain different elements.

```typescript
equalsUnordered([1, 2], [1, 3])
// => false
```

*Compare arrays of objects*

Supports shallow comparison of nested objects.

```typescript
equalsUnordered([{ a: 1 }], [{ a: 1 }])
// => true
```

---

### `intersection`

Compute the intersection of two arrays, meaning the elements that are present
in both arrays.

```typescript
import { intersection } from '@helpers4/array';

intersection<T>(a: readonly T[], b: readonly T[]): T[]
```

**Parameters:**

- `a: readonly T[]` — First array
- `b: readonly T[]` — Second array

**Returns:** `T[]` — The intersection of the two arrays

**Examples:**

*Find common elements*

Returns elements present in both arrays.

```typescript
intersection([1, 2, 3], [2, 3, 4])
// => [2, 3]
```

---

### `intersects`

Simple helper that check if two lists shared at least an item in common.

```typescript
import { intersects } from '@helpers4/array';

intersects<T>(a: readonly T[], b: readonly T[]): boolean
```

**Parameters:**

- `a: readonly T[]` — One list
- `b: readonly T[]` — Another list

**Returns:** `boolean` — `true` if one item is in common, `false` otherwise.

**Examples:**

*Detect shared element*

Returns true when at least one element is shared between both arrays.

```typescript
intersects([1, 2, 3], [3, 4, 5])
// => true
```

*No common elements*

Returns false when no elements are shared.

```typescript
intersects([1, 2], [3, 4])
// => false
```

---

### `partition`

Splits an array into two groups based on a predicate function.
The first group contains elements for which the predicate returns true,
the second group contains the rest.

```typescript
import { partition } from '@helpers4/array';

partition<T>(array: readonly T[], predicate: function): [T[], T[]]
```

**Parameters:**

- `array: readonly T[]` — The array to partition
- `predicate: function` — Function that returns true for elements in the first group

**Returns:** `[T[], T[]]` — A tuple of two arrays: [matching, non-matching]

**Examples:**

*Split numbers by parity*

Splits an array into even and odd numbers using a predicate.

```typescript
partition([1, 2, 3, 4, 5], n => n % 2 === 0)
// => [[2, 4], [1, 3, 5]]
```

*Separate active and inactive users*

Partitions an array of objects based on a boolean property.

```typescript
const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
  { name: 'Charlie', active: true },
];
partition(users, u => u.active)
// => [[Alice, Charlie], [Bob]]
```

*Handle empty array*

Returns two empty arrays when the input is empty.

```typescript
partition([], () => true)
// => [[], []]
```

---

### `range`

Generates an array of sequential numbers from start to end (exclusive).
If only one argument is provided, it generates numbers from 0 to that value.

```typescript
import { range } from '@helpers4/array';

range(startOrEnd: number, end?: number, step?: number): number[]
```

**Parameters:**

- `startOrEnd: number` — The start value (if end is provided) or end value (if end is omitted)
- `end?: number` — The end value (exclusive)
- `step?: number` — The increment between values (default: 1 or -1 depending on direction)

**Returns:** `number[]` — An array of sequential numbers

**Examples:**

*Generate a sequence from 0*

Creates an array of numbers from 0 to n-1 with a single argument.

```typescript
range(5)
// => [0, 1, 2, 3, 4]
```

*Generate a sequence with start and end*

Creates an array from start (inclusive) to end (exclusive).

```typescript
range(1, 5)
// => [1, 2, 3, 4]
```

*Generate a sequence with a custom step*

Creates an array with a specified increment between values.

```typescript
range(0, 10, 2)
// => [0, 2, 4, 6, 8]
```

*Generate a descending sequence*

Automatically produces a descending range when start > end.

```typescript
range(5, 0)
// => [5, 4, 3, 2, 1]
```

---

### `sample`

Picks one or more random elements from an array.
When called without a count, returns a single element or `undefined` if the array is empty.
When called with a count, returns an array of up to `count` random elements sampled without replacement.

```typescript
import { sample } from '@helpers4/array';

sample<T>(array: readonly T[]): T | undefined
```

**Parameters:**

- `array: readonly T[]` — The source array to pick from

**Returns:** `T | undefined` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given

```typescript
import { sample } from '@helpers4/array';

sample<T>(array: readonly T[], count: number): T[]
```

**Parameters:**

- `array: readonly T[]` — The source array to pick from
- `count: number` — Optional number of elements to pick (without replacement)

**Returns:** `T[]` — A single random element (or `undefined`) when no count is given, or an array of random elements when count is given

**Examples:**

*Pick a single random element*

Without a count, returns one random element from the array.

```typescript
sample([1, 2, 3, 4, 5])
// => 3 (random element)
```

*Pick multiple random elements*

With a count, returns an array of random elements sampled without replacement.

```typescript
sample([1, 2, 3, 4, 5], 3)
// => [2, 5, 1] (3 random elements, without replacement)
```

*Empty array returns undefined*

Returns undefined when sampling from an empty array.

```typescript
sample([])
// => undefined
```

---

### `shuffle`

Randomly reorders elements of an array using the Fisher-Yates algorithm.
Returns a new array without mutating the original.

```typescript
import { shuffle } from '@helpers4/array';

shuffle<T>(array: readonly T[]): T[]
```

**Parameters:**

- `array: readonly T[]` — The array to shuffle

**Returns:** `T[]` — A new array with the same elements in random order

**Examples:**

*Shuffle an array of numbers*

Returns a new array with the same elements in random order using the Fisher-Yates algorithm.

```typescript
shuffle([1, 2, 3, 4, 5])
// => [3, 1, 5, 2, 4] (random order)
```

*Original array is not mutated*

The original array remains unchanged.

```typescript
const original = ['a', 'b', 'c'];
const shuffled = shuffle(original);
// original is still ['a', 'b', 'c']
```

---

### `sortNumberAscFn`

Sort numbers in ascending order

**Examples:**

*Sort numbers ascending*

Use sortNumberAscFn as a comparator for Array.sort().

```typescript
[3, 1, 2].sort(sortNumberAscFn)
// => [1, 2, 3]
```

*Sort strings alphabetically*

Use sortStringAscFn for locale-aware string sorting.

```typescript
['banana', 'apple', 'cherry'].sort(sortStringAscFn)
// => ['apple', 'banana', 'cherry']
```

*Sort objects by property*

Use createSortByStringFn to sort objects by a specific string property.

```typescript
const items = [{ name: 'Charlie' }, { name: 'Alice' }, { name: 'Bob' }];
items.sort(createSortByStringFn('name'))
// => [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }]
```

---

### `sortNumberDescFn`

Sort numbers in descending order

---

### `sortStringAscFn`

Sort strings in ascending order

---

### `sortStringAscInsensitiveFn`

Sort strings in ascending order (case insensitive)

---

### `sortStringDescFn`

Sort strings in descending order

---

### `unique`

Removes duplicate values from an array

```typescript
import { unique } from '@helpers4/array';

unique<T>(array: T[]): T[]
```

**Parameters:**

- `array: T[]` — The array to remove duplicates from

**Returns:** `T[]` — New array with unique values only

**Examples:**

*Remove duplicates*

Returns a new array with duplicate values removed.

```typescript
unique([1, 2, 2, 3, 3, 3])
// => [1, 2, 3]
```

---

### `unzip`

Splits an array of tuples into separate arrays, one per position.

The inverse of zip.

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B>(pairs: readonly [A, B][]): [A[], B[]]
```

**Parameters:**

- `pairs: readonly [A, B][]` — Array of 2-tuples to unzip

**Returns:** `[A[], B[]]` — A tuple of two arrays: all first elements and all second elements

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B, C>(pairs: readonly [A, B, C][]): [A[], B[], C[]]
```

**Parameters:**

- `pairs: readonly [A, B, C][]` — Array of 2-tuples to unzip

**Returns:** `[A[], B[], C[]]` — A tuple of two arrays: all first elements and all second elements

```typescript
import { unzip } from '@helpers4/array';

unzip<A, B, C, D>(pairs: readonly [A, B, C, D][]): [A[], B[], C[], D[]]
```

**Parameters:**

- `pairs: readonly [A, B, C, D][]` — Array of 2-tuples to unzip

**Returns:** `[A[], B[], C[], D[]]` — A tuple of two arrays: all first elements and all second elements

**Examples:**

*Split pairs into separate arrays*

The inverse of zip — separate each position into its own array.

```typescript
const pairs: [number, string][] = [[1, 'a'], [2, 'b'], [3, 'c']];
const [nums, letters] = unzip(pairs);

nums;    // => [1, 2, 3]
letters; // => ['a', 'b', 'c']
```

---

### `without`

Returns a new array with all occurrences of the given values removed.

Unlike `difference`, which operates on two arrays as set operands, `without`
uses a variadic API suited for removing known sentinel values inline.
Uses `SameValueZero` equality (same as `Array.prototype.includes`).

```typescript
import { without } from '@helpers4/array';

without<T>(array: readonly T[], values: T[]): T[]
```

**Parameters:**

- `array: readonly T[]` — The source array.
- `values: T[]` — One or more values to exclude from the result.

**Returns:** `T[]` — A new array without the specified values.

**Examples:**

*Remove a single value*

Returns a new array with all occurrences of the given value removed.

```typescript
without([1, 2, 3, 2, 4], 2)
// => [1, 3, 4]
```

*Remove multiple values*

All listed values are excluded from the result.

```typescript
without([1, 2, 3, 2, 4], 2, 3)
// => [1, 4]
```

---

### `zip`

Combines multiple arrays element-by-element into an array of tuples.
The result length equals the length of the shortest input array.

The inverse of unzip.

```typescript
import { zip } from '@helpers4/array';

zip<A, B>(a: readonly A[], b: readonly B[]): [A, B][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array

**Returns:** `[A, B][]` — Array of `[a, b]` pairs

```typescript
import { zip } from '@helpers4/array';

zip<A, B, C>(a: readonly A[], b: readonly B[], c: readonly C[]): [A, B, C][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array
- `c: readonly C[]`

**Returns:** `[A, B, C][]` — Array of `[a, b]` pairs

```typescript
import { zip } from '@helpers4/array';

zip<A, B, C, D>(a: readonly A[], b: readonly B[], c: readonly C[], d: readonly D[]): [A, B, C, D][]
```

**Parameters:**

- `a: readonly A[]` — First array
- `b: readonly B[]` — Second array
- `c: readonly C[]`
- `d: readonly D[]`

**Returns:** `[A, B, C, D][]` — Array of `[a, b]` pairs

**Examples:**

*Pair keys with values*

Combine two arrays element-by-element.

```typescript
zip(['a', 'b', 'c'], [1, 2, 3])
// => [['a', 1], ['b', 2], ['c', 3]]
```

*Truncates to the shorter array*

Stops at the end of the shorter array to avoid undefined entries.

```typescript
zip([1, 2, 3], ['x', 'y'])
// => [[1, 'x'], [2, 'y']]
```

---
