# @helpers4/array

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

## Installation

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

## Usage

```typescript
import { cartesianProduct, chunk, combineSortFns, ... } 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. `null` and `undefined` are treated as empty a |
| `combineSortFns` | Chains multiple sort functions into a single comparator: the first function decides the order unless |
| `compact` | Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array. `null` and  |
| `countBy` | Groups the elements of an array by the key returned by `keyFn` and returns a record mapping each key |
| `createSortByBooleanFn` | Creates a sort function for objects by a boolean property.  Values are coerced with `Boolean()` befo |
| `createSortByDateFn` | Creates a sort function for objects by date property. |
| `createSortByNaturalFn` | Creates a sort function for objects by one or more string properties using natural ordering. Numbers |
| `createSortByNumberFn` | Creates a sort function for objects by number property. |
| `createSortByStringFn` | Creates a sort function for objects by one or more string properties. When multiple properties are g |
| `difference` | Returns the difference between two arrays (items in first array but not in second). `null` and `unde |
| `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. `null` |
| `intersects` | Simple helper that check if two lists shared at least an item in common. `null` and `undefined` are  |
| `isEmpty` | Checks if an array is empty (has no elements). `null` and `undefined` are treated as empty arrays an |
| `isNonEmpty` | Checks if an array is non-empty (has at least one element). `null` and `undefined` are treated as em |
| `max` | Returns the maximum value in an array using a loop instead of spread, avoiding the call stack overfl |
| `mean` | Calculates the arithmetic mean (average) of an array of numbers. Returns `NaN` for an empty array.   |
| `min` | Returns the minimum value in an array using a loop instead of spread, avoiding the call stack overfl |
| `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 |
| `replaceOrAppend` | Returns a new array with the first item matching `predicate` replaced by `item` — or `item` appended |
| `sample` | Picks one or more random elements from an array. When called without a count, returns a single eleme |
| `select` | Filters and transforms an array in a single pass.  Similar to `.filter(condition).map(mapper)` but i |
| `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 |
| `sortStringNaturalAscFn` | Sort strings in ascending order using natural (human-friendly) ordering. Numbers embedded in strings |
| `sortStringNaturalAscInsensitiveFn` | Sort strings in ascending natural order, ignoring case **and diacritics** (`Intl.Collator { sensitiv |
| `sortStringNaturalDescFn` | Sort strings in descending order using natural (human-friendly) ordering. Numbers embedded in string |
| `sortStringNaturalDescInsensitiveFn` | Sort strings in descending natural order, ignoring case **and diacritics** (`Intl.Collator { sensiti |
| `sum` | Calculates the sum of an array of numbers. `null` and `undefined` are treated as empty arrays and re |
| `symmetricDifference` | Returns the symmetric difference between two arrays: items present in exactly one of the two arrays  |
| `toggle` | Returns a new array with `item` removed if present, or appended if absent — the common "toggle a sel |
| `unique` | Removes duplicate values from an array. `null` and `undefined` are treated as empty arrays and retur |
| `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.
`null` and `undefined` are treated as empty arrays and return `[]`.

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

chunk<T>(array: readonly T[] | null | undefined, size: number): T[][]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — 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)
// => []
```

---

### `combineSortFns`

Chains multiple sort functions into a single comparator: the first function
decides the order unless it reports a tie (`0`), in which case the next
function is tried, and so on.

Lets you compose comparators of different kinds — e.g. a boolean-property
comparator from createSortByBooleanFn followed by a string-property
comparator from `createSortByStringFn` — which a single multi-key call
cannot express, since that coerces every key to the same comparison type.

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

combineSortFns<T>(fns: readonly SortFn<T>[]): SortFn<T>
```

**Parameters:**

- `fns: readonly SortFn<T>[]` — Sort functions to try, in priority order. An empty list produces
  a stable no-op comparator (all elements compare equal).

**Returns:** `SortFn<T>` — A single sort function equivalent to applying `fns` in order

**Examples:**

*Default items first, then alphabetical*

Chain a boolean comparator with a string comparator to break ties.

```typescript
const items = [
  { isDefault: false, label: 'Bob' },
  { isDefault: true, label: 'Zoe' },
  { isDefault: false, label: 'Alice' },
];
items.sort(combineSortFns(
  createSortByBooleanFn('isDefault'),
  createSortByStringFn('label'),
))
// => [Zoe (default), Alice, Bob]
```

*Falls through when the first comparator ties*

A `0` result from the first function moves on to the next one.

```typescript
const rows = [{ a: 1, b: 2 }, { a: 1, b: 1 }];
rows.sort(combineSortFns<{ a: number; b: number }>(
  (x, y) => x.a - y.a,
  (x, y) => x.b - y.b,
))
// => [{ a: 1, b: 1 }, { a: 1, b: 2 }]
```

---

### `compact`

Removes all falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an array.
`null` and `undefined` are treated as empty arrays and return `[]`.

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

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

**Parameters:**

- `array: readonly Falsy | T[] | null | undefined` — 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.
`null` and `undefined` are treated as empty arrays and return `{}`.
Items whose computed key is a prototype-polluting string (`__proto__`,
`constructor`, `prototype`) are silently skipped.

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

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

**Parameters:**

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

**Returns:** `Partial<Record<Exclude<K, UnsafeKey>, number>>` — A `Partial<Record<K, number>>` where each key maps to its element count.
  Prototype-polluting keys (`__proto__`, `constructor`, `prototype`) are never present.

**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 }
```

---

### `createSortByBooleanFn`

Creates a sort function for objects by a boolean property.

Values are coerced with `Boolean()` before comparing, so `null`, `undefined`,
`0`, and `''` behave as `false`, and any other truthy value behaves as `true`.

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

createSortByBooleanFn<T extends Record<string, unknown>>(property: keyof T, trueFirst: boolean): SortFn<T>
```

**Parameters:**

- `property: keyof T` — The property to sort by.
- `trueFirst: boolean` (default: `true`) — Whether `true` values sort before `false` values (default: `true`).

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

**Examples:**

*Sort objects with true values first*

Default items float to the top of the list.

```typescript
const items = [{ isDefault: false }, { isDefault: true }, { isDefault: false }];
items.sort(createSortByBooleanFn('isDefault'))
// => [{ isDefault: true }, { isDefault: false }, { isDefault: false }]
```

*Sort with false values first*

Pass trueFirst = false to invert the priority.

```typescript
const items = [{ archived: true }, { archived: false }];
items.sort(createSortByBooleanFn('archived', false))
// => [{ archived: false }, { archived: true }]
```

---

### `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'`).
  Accepted value types: `Date` (including cross-realm instances), `string`, or
  `number` (Unix milliseconds). Any object with a `getTime(): number` method is
  also accepted (duck-typed, so cross-realm `Date` objects work correctly).
  `null`, `undefined`, and unparseable strings produce `NaN` and sort last,
  distinct from a genuine Unix-epoch date (`new Date(0)`).

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

**Examples:**

*createSortByDateFn*

```typescript
```ts
const events = [
  { date: new Date('2023-01-01') },
  { date: new Date('2021-06-15') },
];
events.sort(createSortByDateFn('date'))
// => sorted oldest first
```
```

---

### `createSortByNaturalFn`

Creates a sort function for objects by one or more string properties using
natural ordering. Numbers embedded in values are compared numerically:
"W2" < "W11" < "W20". When multiple properties are given, ties on the
first key are broken by the second key, then the third, and so on.

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

createSortByNaturalFn<T extends Record<string, unknown>>(property?: keyof T | readonly keyof T[], caseInsensitive: boolean): SortFn<T>
```

**Parameters:**

- `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by.
  Defaults to trying 'value', 'label', 'title', 'description' in that order.
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case **and diacritics** (default: false).
  Uses `Intl.Collator { sensitivity: 'base' }`, which treats é, E, and e as equal.
  This differs from `createSortByStringFn(key, true)`, which only folds case and
  still distinguishes accented characters (é ≠ e).

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

**Examples:**

*createSortByNaturalFn*

```typescript
```ts
const items = [{ label: 'W11' }, { label: 'W2' }, { label: 'W20' }];
items.sort(createSortByNaturalFn('label'))
// => [{ label: 'W2' }, { label: 'W11' }, { label: 'W20' }]
```
```

---

### `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'`). Always pass an
  explicit key when T does not have a `'value'` property — omitting it on such types
  produces a no-op comparator (all elements compare equal).
  `null` and `undefined` sort last (treated as `+Infinity`). Non-numeric values
  (including booleans — `Number(true) === 1`, `Number(false) === 0`) and `NaN` also
  sort last.

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

**Examples:**

*createSortByNumberFn*

```typescript
```ts
const items = [{ count: 3 }, { count: 1 }, { count: 2 }];
items.sort(createSortByNumberFn('count'))
// => [{ count: 1 }, { count: 2 }, { count: 3 }]
```
```

---

### `createSortByStringFn`

Creates a sort function for objects by one or more string properties.
When multiple properties are given the array is sorted by the first key;
ties are broken by the second key, then the third, and so on.

Property values are coerced to strings via `String()` before comparison:
numbers sort as `'0'`, `'1'`, `'42'`, etc. (lexicographic, not numeric);
use `createSortByNumberFn` for numeric properties.

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

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

**Parameters:**

- `property?: keyof T | readonly keyof T[]` — The property (or ordered list of properties) to sort by.
  Defaults to trying 'value', 'label', 'title', 'description' in that order.
  Pass `undefined` explicitly to use auto-detect; an empty array `[]` produces a
  stable no-op comparator (does **not** fall back to auto-detect).
- `caseInsensitive: boolean` (default: `false`) — Whether to ignore case (default: false).
  Uses `Intl.Collator { sensitivity: 'accent' }`, which folds case but still
  distinguishes accented characters (é ≠ e). This differs from
  `createSortByNaturalFn(key, true)`, which also collapses diacritics.

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

**Examples:**

*Sort objects by string 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' }]
```

*Sort objects by multiple keys*

Pass an array of keys; ties on the first key are broken by the next.

```typescript
const rows = [
  { dept: 'B', name: 'Alice' },
  { dept: 'A', name: 'Zoe' },
  { dept: 'B', name: 'Adam' },
  { dept: 'A', name: 'Anna' },
];
rows.sort(createSortByStringFn(['dept', 'name'] as const))
// => A:Anna, A:Zoe, B:Adam, B:Alice
```

---

### `difference`

Returns the difference between two arrays (items in first array but not in second).
`null` and `undefined` are treated as empty arrays:
`difference(null, b)` → `[]`; `difference(a, null)` → copy of `a`.

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

difference<T>(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array1: readonly T[] | null | undefined` — First array
- `array2: readonly T[] | null | undefined` — 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.
`null` and `undefined` are treated as empty arrays and return `[]`.

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

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

**Parameters:**

- `a: readonly T[] | null | undefined` — First array
- `b: readonly T[] | null | undefined` — 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.
`null` and `undefined` are treated as empty arrays and return `false`.

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

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

**Parameters:**

- `a: readonly T[] | null | undefined` — One list
- `b: readonly T[] | null | undefined` — 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
```

---

### `isEmpty`

Checks if an array is empty (has no elements).
`null` and `undefined` are treated as empty arrays and return `true`.

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

isEmpty(value: readonly unknown[] | null | undefined): value is readonly never[] | null | undefined
```

**Parameters:**

- `value: readonly unknown[] | null | undefined` — The array to check

**Returns:** `value is readonly never[] | null | undefined` — `true` if the array has no elements, or if `value` is `null`/`undefined`

**Examples:**

*Check if an array is empty*

Returns true only for arrays with no elements.

```typescript
isEmpty([])        // => true
isEmpty([1, 2, 3]) // => false
isEmpty([null])    // => false  (null is still an element)
```

*Guard before accessing first element*

Use isEmpty as an early-return guard for arrays, null, and undefined; the false branch is safely non-empty.

```typescript
function first<T>(arr: T[] | null | undefined): T | undefined {
  if (isEmpty(arr)) return undefined;
  return arr[0];
}
first([])        // => undefined
first(null)      // => undefined
first([1, 2])    // => 1
```

---

### `isNonEmpty`

Checks if an array is non-empty (has at least one element).
`null` and `undefined` are treated as empty arrays and return `false`.

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

isNonEmpty<T>(value: readonly T[] | null | undefined): value is readonly [T, T]
```

**Parameters:**

- `value: readonly T[] | null | undefined` — The array to check

**Returns:** `value is readonly [T, T]` — `true` if the array has at least one element; `false` for empty, `null`, or `undefined`

**Examples:**

*Check if an array has elements*

Returns true for arrays with at least one element, regardless of the element values.

```typescript
isNonEmpty([1, 2, 3]) // => true
isNonEmpty([null])    // => true  (null is still an element)
isNonEmpty([])        // => false
```

*Safe first-element access with type narrowing*

In the true branch, the type narrows to [T, ...T[]], making arr[0] always defined.

```typescript
function first<T>(arr: readonly T[]): T | undefined {
  if (isNonEmpty(arr)) return arr[0]; // arr[0] is T, not T | undefined
  return undefined;
}
first([1, 2]) // => 1
first([])     // => undefined
```

---

### `max`

Returns the maximum value in an array using a loop instead of spread,
avoiding the call stack overflow that occurs with `Math.max(...array)`
for very large arrays (> ~65 000 elements).
`null` and `undefined` are treated as empty arrays and return `undefined`.

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

max(array: readonly number[] | null | undefined): number | undefined
```

**Parameters:**

- `array: readonly number[] | null | undefined` — Array of numbers

**Returns:** `number | undefined` — Maximum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN`

**Examples:**

*Safe maximum for large arrays*

Unlike Math.max(...array), max() uses a loop and handles arrays of any size without stack overflow.

```typescript
max([3, 1, 4, 1, 5, 9])
// => 9

// Safe for 1 000 000+ elements (Math.max(...arr) would throw):
max(Array.from({ length: 1_000_000 }, (_, i) => i))
// => 999999
```

---

### `mean`

Calculates the arithmetic mean (average) of an array of numbers.
Returns `NaN` for an empty array.

Pairs with sum for aggregate operations.

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

mean(array: readonly number[]): number
```

**Parameters:**

- `array: readonly number[]` — The array of numbers to average

**Returns:** `number` — The arithmetic mean, or `NaN` if the array is empty

**Examples:**

*Average a list of numbers*

Returns the arithmetic mean of the array; NaN for empty arrays.

```typescript
mean([1, 2, 3, 4])  // => 2.5
mean([10, 20, 30])  // => 20
mean([])            // => NaN
```

---

### `min`

Returns the minimum value in an array using a loop instead of spread,
avoiding the call stack overflow that occurs with `Math.min(...array)`
for very large arrays (> ~65 000 elements).
`null` and `undefined` are treated as empty arrays and return `undefined`.

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

min(array: readonly number[] | null | undefined): number | undefined
```

**Parameters:**

- `array: readonly number[] | null | undefined` — Array of numbers

**Returns:** `number | undefined` — Minimum value, `undefined` for empty arrays, `null`, `undefined`, or `NaN` if any element is `NaN`

**Examples:**

*Safe minimum for large arrays*

Unlike Math.min(...array), min() uses a loop and handles arrays of any size without stack overflow.

```typescript
min([3, 1, 4, 1, 5, 9])
// => 1

// Safe for 1 000 000+ elements (Math.min(...arr) would throw):
min(Array.from({ length: 1_000_000 }, (_, i) => i))
// => 0
```

---

### `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.
`null` and `undefined` are treated as empty arrays and return `[[], []]`.

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

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

**Parameters:**

- `array: readonly T[] | null | undefined` — 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]
```

---

### `replaceOrAppend`

Returns a new array with the first item matching `predicate` replaced by
`item` — or `item` appended at the end if no match is found. The common
"upsert into a list" pattern.

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

replaceOrAppend<T>(array: readonly T[] | null | undefined, item: T, predicate: function): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty.
- `item: T` — The replacement (or new) item
- `predicate: function` — Called with each existing item to find what to replace

**Returns:** `T[]` — A new array with `item` upserted

**Examples:**

*Upsert an item into a list*

Replaces the first match, or appends when nothing matches.

```typescript
const users = [{ id: 1, name: 'a' }, { id: 2, name: 'b' }];
replaceOrAppend(users, { id: 1, name: 'A' }, (u) => u.id === 1)
// => [{ id: 1, name: 'A' }, { id: 2, name: 'b' }]
```

*Appends when no item matches*

A missing id is added at the end rather than silently dropped.

```typescript
replaceOrAppend([{ id: 1 }], { id: 2 }, (u) => u.id === 2)
// => [{ id: 1 }, { id: 2 }]
```

---

### `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[] | null | undefined): T | undefined
```

**Parameters:**

- `array: readonly T[] | null | undefined` — 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[] | null | undefined, count: number): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — 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
```

---

### `select`

Filters and transforms an array in a single pass.

Similar to `.filter(condition).map(mapper)` but iterates the array only once.
**Index semantics differ from `.filter().map()`:** the `index` passed to both
`condition` and `mapper` is the index in the **original** array, not the
post-filter position. Use index-agnostic callbacks when the two must behave
identically.

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

select<T, U>(array: readonly T[] | null | undefined, mapper: function, condition: function): U[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The array to process
- `mapper: function` — Transforms each item that passes the condition
- `condition: function` (default: `...`) — Determines which items to include; defaults to keeping all items

**Returns:** `U[]` — Mapped values for items that pass the condition

**Examples:**

*Filter and transform in one pass*

Keeps only items matching the condition and transforms them — equivalent to .filter().map() but with a single iteration.

```typescript
select([1, 2, 3, 4, 5], x => x * 2, x => x % 2 === 0)
// => [4, 8]
```

*Extract a field from matching objects*

Filter on a condition and pluck a specific property in a single readable call.

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

---

### `shuffle`

Randomly reorders elements of an array using the Fisher-Yates algorithm.
Returns a new array without mutating the original.
`null` and `undefined` are treated as empty arrays and return `[]`.

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

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

**Parameters:**

- `array: readonly T[] | null | undefined` — 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']
```

---

### `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

---

### `sortStringNaturalAscFn`

Sort strings in ascending order using natural (human-friendly) ordering.
Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20".

**Examples:**

*Natural sort for strings with embedded numbers*

sortStringNaturalAscFn treats numeric parts as numbers: "W2" < "W11" < "W20".

```typescript
['W20', 'W2', 'W11', 'W01'].sort(sortStringNaturalAscFn)
// => ['W01', 'W2', 'W11', 'W20']
```

*Natural sort for object arrays*

createSortByNaturalFn sorts objects with embedded numbers in property values.

```typescript
const items = [{ code: 'W20' }, { code: 'W2' }, { code: 'W11' }, { code: 'W01' }];
items.sort(createSortByNaturalFn('code'))
// => W01, W2, W11, W20
```

---

### `sortStringNaturalAscInsensitiveFn`

Sort strings in ascending natural order, ignoring case **and diacritics**
(`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal).
Numbers embedded in strings are compared numerically: "W2" < "W11" < "W20".

**Examples:**

*sortStringNaturalAscInsensitiveFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalAscInsensitiveFn) // => ['W2', 'W11', 'W20']
```
```

---

### `sortStringNaturalDescFn`

Sort strings in descending order using natural (human-friendly) ordering.
Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2".

**Examples:**

*sortStringNaturalDescFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalDescFn) // => ['W20', 'W11', 'W2']
```
```

---

### `sortStringNaturalDescInsensitiveFn`

Sort strings in descending natural order, ignoring case **and diacritics**
(`Intl.Collator { sensitivity: 'base' }` — treats é, E, and e as equal).
Numbers embedded in strings are compared numerically: "W20" > "W11" > "W2".

**Examples:**

*sortStringNaturalDescInsensitiveFn*

```typescript
```ts
['W11', 'W2', 'W20'].sort(sortStringNaturalDescInsensitiveFn) // => ['W20', 'W11', 'W2']
```
```

---

### `sum`

Calculates the sum of an array of numbers.
`null` and `undefined` are treated as empty arrays and return `0`.

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

sum(array: readonly number[] | null | undefined): number
```

**Parameters:**

- `array: readonly number[] | null | undefined` — The array of numbers to sum

**Returns:** `number` — The sum of all values, or `0` for an empty array, `null`, or `undefined`

**Examples:**

*Sum numbers*

Calculates the sum of an array of numbers.

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

*Sum with negative numbers*

Handles negative numbers correctly.

```typescript
sum([10, -3, 5, -2])
// => 10
```

---

### `symmetricDifference`

Returns the symmetric difference between two arrays: items present in
exactly one of the two arrays (in either, but not both).
`null` and `undefined` are treated as empty arrays.

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

symmetricDifference<T>(array1: readonly T[] | null | undefined, array2: readonly T[] | null | undefined): T[]
```

**Parameters:**

- `array1: readonly T[] | null | undefined` — First array
- `array2: readonly T[] | null | undefined` — Second array

**Returns:** `T[]` — Items unique to `array1` (in original order), followed by items unique to `array2`

**Examples:**

*Find items unique to either array*

Complements difference()/intersection() with the "in either, not both" set operation.

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

*Identical arrays have no symmetric difference*

Items shared by both arrays are excluded entirely.

```typescript
symmetricDifference(['a', 'b'], ['a', 'b'])
// => []
```

---

### `toggle`

Returns a new array with `item` removed if present, or appended if absent —
the common "toggle a selection" pattern.

By default, presence is checked with `SameValueZero` equality (like
`Array.prototype.includes`). Pass `key` to compare by a derived identity
instead — useful for toggling objects by id rather than by reference.

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

toggle<T>(array: readonly T[] | null | undefined, item: T, key?: function): T[]
```

**Parameters:**

- `array: readonly T[] | null | undefined` — The source array. `null`/`undefined` are treated as empty.
- `item: T` — The item to add or remove
- `key?: function` — Optional function deriving the identity to compare by

**Returns:** `T[]` — A new array with `item` toggled

**Examples:**

*Toggle a selection*

Common pattern for multi-select UI state — add if absent, remove if present.

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

*Toggle objects by a derived key*

Pass a key selector to compare by id instead of object reference.

```typescript
const selected = [{ id: 1 }, { id: 2 }];
toggle(selected, { id: 1 }, (x) => x.id)
// => [{ id: 2 }]
```

---

### `unique`

Removes duplicate values from an array.
`null` and `undefined` are treated as empty arrays and return `[]`.

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

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

**Parameters:**

- `array: readonly T[] | null | undefined` — 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][] | null | undefined): [A[], B[]]
```

**Parameters:**

- `pairs: readonly [A, B][] | null | undefined` — 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][] | null | undefined): [A[], B[], C[]]
```

**Parameters:**

- `pairs: readonly [A, B, C][] | null | undefined` — 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][] | null | undefined): [A[], B[], C[], D[]]
```

**Parameters:**

- `pairs: readonly [A, B, C, D][] | null | undefined` — 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`).
`null` and `undefined` are treated as empty arrays and return `[]`.

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

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

**Parameters:**

- `array: readonly T[] | null | undefined` — 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']]
```

---
