@absoluteui/helpers/array
Grouping, chunking, sorting, searching, pagination, immutable edits, and tree building for arrays.
import { ... } from '@absoluteui/helpers/array';How to use
Section titled “How to use”Immutable helpers for the lists an application shows: group, sort, page, summarise, and edit arrays without mutating them, and turn flat parent/child rows into trees. Every function returns a new array. For query-style chains over large or lazy sequences, see helpers/linq; the Helpers and services guide covers the shared conventions.
How do I group employees by department and show one page of the result?
Section titled “How do I group employees by department and show one page of the result?”Sort first (stable, multi-key, locale-aware), then page with paginate; groupBy and sumBy give the per-department figures for a summary row.
import { groupBy, paginate, sortBy, sumBy } from '@absoluteui/helpers/array';
const staff = [ { name: 'Asha', dept: 'Finance', salary: 5200 }, { name: 'Ben', dept: 'HR', salary: 4100 }, { name: 'Chen', dept: 'Finance', salary: 6100 }, { name: 'Dana', dept: 'IT', salary: 7000 }, { name: 'Eli', dept: 'HR', salary: 3900 },];const sorted = sortBy(staff, [{ by: (e) => e.dept }, { by: (e) => e.salary, order: 'desc' }], { locale: 'en' });const page = paginate(sorted, 1, 2);page.items.map((e) => e.name); // => ['Chen', 'Asha'][page.pageCount, page.hasNext]; // => [3, true]const byDept = groupBy(staff, (e) => e.dept);sumBy(byDept.HR ?? [], (e) => e.salary); // => 8000How do I update a list held in state without mutating it?
Section titled “How do I update a list held in state without mutating it?”upsertBy replaces the row with the same key or appends it; removeAt, move, and toggleItem cover deletes, drag-reorder, and multi-select.
import { move, toggleItem, upsertBy } from '@absoluteui/helpers/array';
const rows = [{ id: 1, status: 'draft' }, { id: 2, status: 'draft' }];const saved = upsertBy(rows, { id: 2, status: 'submitted' }, (r) => r.id);saved.map((r) => r.status); // => ['draft', 'submitted']rows[1]?.status; // => 'draft'move(['Mon', 'Tue', 'Wed'], 2, 0); // => ['Wed', 'Mon', 'Tue']toggleItem([3, 7], 7); // => [3]How do I build an org tree from flat rows?
Section titled “How do I build an org tree from flat rows?”buildTree links rows by id and parentId (field names or selectors) and reports orphans and cycles instead of looping; flattenTree turns it back into indented rows.
import { buildTree, flattenTree } from '@absoluteui/helpers/array';
const people = [ { id: 'ceo', parentId: null, name: 'Maya' }, { id: 'cfo', parentId: 'ceo', name: 'Omar' }, { id: 'acct', parentId: 'cfo', name: 'Lin' }, { id: 'temp', parentId: 'gone', name: 'Sam' },];const tree = buildTree(people, { orphans: 'omit' });tree.orphans.map((p) => p.name); // => ['Sam']flattenTree(tree.roots).map((e) => e.depth); // => [0, 1, 2]How do I process records in batches?
Section titled “How do I process records in batches?”chunk splits work into fixed-size batches, for example to import rows 500 at a time; partition separates valid from invalid rows in one pass.
import { chunk, partition } from '@absoluteui/helpers/array';
const ids = Array.from({ length: 1200 }, (_, i) => i + 1);chunk(ids, 500).map((batch) => batch.length); // => [500, 500, 200]const [valid, invalid] = partition(['E-1', '', 'E-3'], (id) => id !== '');[valid.length, invalid.length]; // => [2, 1]What goes wrong?
Section titled “What goes wrong?”Sizes and indexes are validated, and generated arrays are capped at MAX_GENERATED_LENGTH:
import { chunk, range } from '@absoluteui/helpers/array';
chunk([1, 2, 3], 0); // => throws HLP_ARRAY_INVALID_SIZErange(0, 1e9); // => throws HLP_ARRAY_TOO_LARGEAPI reference
Section titled “API reference”General
Section titled “General”Functions
Section titled “Functions”arraysEqual
Shallow element-wise equality (default Object.is).
function arraysEqual<T>(a: readonly T[], b: readonly T[], equals?: (x: T, y: T) => boolean): boolean;Example:
arraysEqual([1, 2], [1, 2]); // => trueaverageBy
Mean of fn over items, or undefined for an empty array.
function averageBy<T>(items: readonly T[], fn: (item: T, index: number) => number): number | undefined;Example:
averageBy([{ n: 2 }, { n: 4 }], (x) => x.n); // => 3binarySearch
Index of target in a sorted array, or ~insertionPoint (negative) when absent.
function binarySearch<T>(sorted: readonly T[], target: T, compare?: Comparer<T>): number;Example:
binarySearch([1, 3, 5], 3); // => 1~binarySearch([1, 3, 5], 4); // => 2bottomN
The n smallest items, smallest first (stable for ties). O(len log n).
function bottomN<T>(items: readonly T[], n: number, compare?: Comparer<T>): T[];Example:
bottomN([5, 1, 9, 3], 2); // => [1, 3]chunk
Splits into chunks of size; the last chunk may be shorter.
function chunk<T>(items: readonly T[], size: number): T[][];Example:
chunk([1, 2, 3, 4, 5], 2); // => [[1, 2], [3, 4], [5]]compact
Removes null, undefined, and false (keeps 0, '', and NaN).
function compact<T>(items: readonly T[]): Exclude<T, null | undefined | false>[];Example:
compact([0, null, 'a', false, undefined]); // => [0, 'a']countBy
Counts items per key into a null-prototype record.
function countBy<T, K extends PropertyKey>(items: Iterable<T>, keyFn: (item: T, index: number) => K): Partial<Record<K, number>>;Example:
countBy(['a', 'bb', 'cc'], (s) => s.length); // => { 1: 1, 2: 2 }dropWhile
Items after the leading run where predicate holds.
function dropWhile<T>(items: readonly T[], predicate: (item: T, index: number) => boolean): T[];Example:
dropWhile([1, 2, 5, 1], (n) => n < 3); // => [5, 1]first
First item, or undefined.
function first<T>(items: readonly T[]): T | undefined;Example:
first([1, 2]); // => 1flatten
Flattens nested arrays up to depth (default 1).
function flatten<A extends readonly unknown[], D extends number = 1>(items: A, depth?: D): FlatArray<A, D>[];Example:
flatten([1, [2, [3]]]); // => [1, 2, [3]]flattenDeep
Flattens all nesting levels.
function flattenDeep<A extends readonly unknown[]>(items: A): FlatArray<A, 20>[];Example:
flattenDeep([1, [2, [3, [4]]]]); // => [1, 2, 3, 4]groupBy
Groups into a null-prototype record keyed by keyFn (native Object.groupBy when present).
function groupBy<T, K extends PropertyKey>(items: Iterable<T>, keyFn: (item: T, index: number) => K): Partial<Record<K, T[]>>;Example:
groupBy(['one', 'two', 'three'], (s) => s.length); // => { 3: ['one', 'two'], 5: ['three'] }groupByToMap
Groups into a Map (native Map.groupBy when present); keys use SameValueZero.
function groupByToMap<T, K>(items: Iterable<T>, keyFn: (item: T, index: number) => K): Map<K, T[]>;Example:
groupByToMap([{ d: 1 }, { d: 2 }, { d: 1 }], (x) => x.d).get(1)?.length; // => 2insertAt
Inserts items before index (index === length appends; negative counts from the end).
function insertAt<T>(items: readonly T[], index: number, ...values: T[]): T[];Example:
insertAt([1, 4], 1, 2, 3); // => [1, 2, 3, 4]intersperse
Inserts separator between items.
function intersperse<T, S>(items: readonly T[], separator: S): (T | S)[];Example:
intersperse(['a', 'b', 'c'], ','); // => ['a', ',', 'b', ',', 'c']keyBy
Indexes items by key into a null-prototype record; later items win.
function keyBy<T, K extends PropertyKey>(items: Iterable<T>, keyFn: (item: T, index: number) => K): Partial<Record<K, T>>;Example:
keyBy([{ id: 'a' }, { id: 'b' }], (x) => x.id); // => { a: { id: 'a' }, b: { id: 'b' } }last
Last item, or undefined.
function last<T>(items: readonly T[]): T | undefined;Example:
last([1, 2]); // => 2lowerBound
First index whose element is not less than target.
function lowerBound<T>(sorted: readonly T[], target: T, compare?: Comparer<T>): number;Example:
lowerBound([1, 2, 2, 3], 2); // => 1maxBy
Item with the largest key (first on ties; empty keys ignored), or undefined.
function maxBy<T>(items: readonly T[], fn: (item: T) => unknown): T | undefined;Example:
maxBy([{ n: 2 }, { n: 1 }], (x) => x.n); // => { n: 2 }minBy
Item with the smallest key (first on ties; empty keys ignored), or undefined.
function minBy<T>(items: readonly T[], fn: (item: T) => unknown): T | undefined;Example:
minBy([{ n: 2 }, { n: 1 }], (x) => x.n); // => { n: 1 }move
Moves the item at from to to (negative indexes count from the end).
function move<T>(items: readonly T[], from: number, to: number): T[];Example:
move(['a', 'b', 'c'], 0, 2); // => ['b', 'c', 'a']nth
Item at index (negative counts from the end), or undefined.
function nth<T>(items: readonly T[], index: number): T | undefined;Example:
nth([1, 2, 3], -2); // => 2paginate
Slices one 1-based page. Pages past the end have no items.
function paginate<T>(items: readonly T[], page: number, pageSize: number): Page<T>;Example:
paginate(range(25), 3, 10); // => { items: [20, 21, 22, 23, 24], total: 25, page: 3, pageSize: 10, pageCount: 3, hasNext: false, hasPrev: true }pairwise
Adjacent pairs.
function pairwise<T>(items: readonly T[]): [T, T][];Example:
pairwise([1, 2, 3]); // => [[1, 2], [2, 3]]partition
Splits into [matching, rest], preserving order.
function partition<T, S extends T>(items: readonly T[], predicate: (item: T, index: number) => item is S): [S[], Exclude<T, S>[]];function partition<T>(items: readonly T[], predicate: (item: T, index: number) => boolean): [T[], T[]];Example:
partition([1, 2, 3, 4], (n) => n % 2 === 0); // => [[2, 4], [1, 3]]range
Numbers from start (inclusive) to end (exclusive) by step. range(n) is 0..n-1.
Throws HLP_ARRAY_INVALID_RANGE for non-finite input or a zero step, and HLP_ARRAY_TOO_LARGE above MAX_GENERATED_LENGTH.
function range(start: number, end?: number, step?: number): number[];Example:
range(3); // => [0, 1, 2]range(10, 0, -5); // => [10, 5]range(0, 1, 0.25); // => [0, 0.25, 0.5, 0.75]removeAt
Removes count items starting at index (negative counts from the end).
function removeAt<T>(items: readonly T[], index: number, count?: number): T[];Example:
removeAt([1, 2, 3, 4], 1, 2); // => [1, 4]repeat
An array of count copies of value.
function repeat<T>(value: T, count: number): T[];Example:
repeat('x', 3); // => ['x', 'x', 'x']replaceAt
Replaces the item at index (negative counts from the end).
function replaceAt<T>(items: readonly T[], index: number, value: T): T[];Example:
replaceAt([1, 2, 3], -1, 9); // => [1, 2, 9]rotate
Rotates by n positions: positive moves items toward the end, negative toward the start.
function rotate<T>(items: readonly T[], n: number): T[];Example:
rotate([1, 2, 3, 4], 1); // => [4, 1, 2, 3]rotate([1, 2, 3, 4], -1); // => [2, 3, 4, 1]sortBy
Stable multi-key sort. Keys are evaluated once per item.
function sortBy<T>(items: readonly T[], keys: ((item: T) => unknown) | SortKey<T> | readonly (((item: T) => unknown) | SortKey<T>)[], options?: SortByOptions): T[];Example:
const people = [{ name: 'Ada', dept: 'Vertrieb', salary: 50 }, { name: 'Ben', dept: 'Ärzte', salary: 40 }, { name: 'Cy', dept: 'Vertrieb', salary: 70 }];sortBy(people, [{ by: (p) => p.dept }, { by: (p) => p.salary, order: 'desc' }], { locale: 'de' }).map((p) => p.name); // => ['Ben', 'Cy', 'Ada']splitAt
Splits into [before, fromIndex] (negative counts from the end; out of range clamps).
function splitAt<T>(items: readonly T[], index: number): [T[], T[]];Example:
splitAt([1, 2, 3], 1); // => [[1], [2, 3]]sumBy
Sum of fn over items with compensated (Neumaier) summation.
function sumBy<T>(items: readonly T[], fn: (item: T, index: number) => number): number;Example:
sumBy([{ n: 1e100 }, { n: 1 }, { n: -1e100 }], (x) => x.n); // => 1 // plain addition gives 0swap
Swaps two items.
function swap<T>(items: readonly T[], i: number, j: number): T[];Example:
swap([1, 2, 3], 0, 2); // => [3, 2, 1]takeWhile
Leading items while predicate holds.
function takeWhile<T>(items: readonly T[], predicate: (item: T, index: number) => boolean): T[];Example:
takeWhile([1, 2, 5, 1], (n) => n < 3); // => [1, 2]times
Calls fn count times and collects the results.
function times<T>(count: number, fn: (index: number) => T): T[];Example:
times(3, (i) => i * 2); // => [0, 2, 4]toggleItem
Removes every item equal to item, or appends it when absent.
function toggleItem<T>(items: readonly T[], item: T, equals?: (a: T, b: T) => boolean): T[];Example:
toggleItem([1, 2], 2); // => [1]toggleItem([1, 2], 3); // => [1, 2, 3]topN
The n largest items, largest first (stable for ties). O(len log n).
function topN<T>(items: readonly T[], n: number, compare?: Comparer<T>): T[];Example:
topN([5, 1, 9, 3], 2); // => [9, 5]const users = [{ name: 'Ada', score: 7 }, { name: 'Ben', score: 9 }, { name: 'Cy', score: 4 }];topN(users, 2, (a, b) => a.score - b.score).map((u) => u.name); // => ['Ben', 'Ada']uniq
Removes duplicates (SameValueZero), keeping first occurrences.
function uniq<T>(items: readonly T[]): T[];Example:
uniq([1, 2, 1, NaN, NaN]); // => [1, 2, NaN]uniqBy
Removes items with a duplicate key, keeping first occurrences.
function uniqBy<T>(items: readonly T[], keyFn: (item: T) => unknown): T[];Example:
uniqBy([{ id: 1 }, { id: 1 }, { id: 2 }], (x) => x.id); // => [{ id: 1 }, { id: 2 }]unzip
Inverse of zip: splits tuples into one array per position (width of the longest row).
function unzip<const T extends readonly unknown[]>(rows: readonly T[]): { -readonly [K in keyof T]: T[K][];};Example:
unzip([[1, 'a'], [2, 'b']] as [number, string][]); // => [[1, 2], ['a', 'b']]upperBound
First index whose element is greater than target.
function upperBound<T>(sorted: readonly T[], target: T, compare?: Comparer<T>): number;Example:
upperBound([1, 2, 2, 3], 2); // => 3upsertBy
Replaces the first item with the same key, or appends.
function upsertBy<T>(items: readonly T[], item: T, keyFn: (item: T) => unknown): T[];Example:
upsertBy([{ id: 1, v: 'a' }], { id: 1, v: 'b' }, (x) => x.id); // => [{ id: 1, v: 'b' }]windowed
Sliding windows of size, advancing by step; only full windows are returned.
function windowed<T>(items: readonly T[], size: number, step?: number): T[][];Example:
windowed([1, 2, 3, 4], 2); // => [[1, 2], [2, 3], [3, 4]]windowed([1, 2, 3, 4, 5], 2, 2); // => [[1, 2], [3, 4]]zip
Combines arrays element-wise into tuples; stops at the shortest array.
function zip<const T extends readonly (readonly unknown[])[]>(...arrays: T): { -readonly [K in keyof T]: T[K][number];}[];Example:
zip([1, 2, 3], ['a', 'b']); // => [[1, 'a'], [2, 'b']]zipWith
Combines two arrays element-wise with fn; stops at the shorter array.
function zipWith<A, B, R>(a: readonly A[], b: readonly B[], fn: (a: A, b: B, index: number) => R): R[];Example:
zipWith([1, 2], [10, 20], (a, b) => a + b); // => [11, 22]Constants
Section titled “Constants”MAX_GENERATED_LENGTH
Maximum element count produced by range, times, and repeat.
const MAX_GENERATED_LENGTH = 10000000;Interfaces
Section titled “Interfaces”Page
One page of a list.
interface Page<T> { readonly items: T[]; readonly total: number; readonly page: number; readonly pageSize: number; readonly pageCount: number; readonly hasNext: boolean; readonly hasPrev: boolean;}SortByOptions
interface SortByOptions { readonly collator?: Intl.Collator; readonly locale?: string | readonly string[]; readonly nulls?: 'first' | 'last';}SortKey
One sort key: a selector with direction, null placement, and string ordering.
interface SortKey<T> { readonly by: (item: T) => unknown; readonly order?: 'asc' | 'desc'; readonly nulls?: 'first' | 'last'; readonly collator?: Intl.Collator; readonly compare?: Comparer<unknown>;}Comparer
Element comparer: negative, zero, or positive.
type Comparer<T> = (a: T, b: T) => number;Functions
Section titled “Functions”buildTree
Builds a tree from id/parentId records in input order. Throws HLP_ARRAY_DUPLICATE_ID on repeated ids.
function buildTree<T extends object>(items: readonly T[], options?: BuildTreeOptions<T>): BuildTreeResult<T>;Example:
const { roots } = buildTree([{ id: 1, parentId: null }, { id: 2, parentId: 1 }]);roots[0]?.children[0]?.item.id; // => 2flattenTree
Flattens a tree into entries with depth and parent. Throws HLP_ARRAY_TREE_CYCLE when a node is reached twice.
function flattenTree<N>(roots: Iterable<N>, options?: FlattenTreeOptions<N>): FlatTreeEntry<N>[];Example:
const rows = [{ id: 1, parentId: null, name: 'CEO' }, { id: 2, parentId: 1, name: 'CTO' }, { id: 3, parentId: 1, name: 'CFO' }];flattenTree(buildTree(rows).roots).map((e) => `${' '.repeat(e.depth)}${e.node.item.name}`); // => ['CEO', ' CTO', ' CFO']Interfaces
Section titled “Interfaces”BuildTreeOptions
interface BuildTreeOptions<T> { readonly id?: KeySpec<T>; readonly parentId?: KeySpec<T>; readonly orphans?: 'root' | 'omit';}BuildTreeResult
interface BuildTreeResult<T> { readonly roots: TreeNode<T>[]; readonly orphans: T[]; readonly cycles: T[];}FlattenTreeOptions
interface FlattenTreeOptions<N> { readonly children?: (node: N) => Iterable<N> | null | undefined; readonly order?: 'depth' | 'breadth';}FlatTreeEntry
interface FlatTreeEntry<N> { readonly node: N; readonly depth: number; readonly parent: N | undefined;}TreeNode
A tree node wrapping the original item.
interface TreeNode<T> { readonly item: T; readonly children: TreeNode<T>[];}