@rimbu/base
Version:
Utilities to implement Rimbu collections
313 lines (256 loc) • 7.9 kB
text/typescript
import { TraverseState, Update, type ArrayNonEmpty } from '@rimbu/common';
export function _appendNew<T>(array: readonly T[], value: T): ArrayNonEmpty<T> {
return array.toSpliced(array.length, 0, value) as ArrayNonEmpty<T>;
}
export function _appendOld<T>(array: readonly T[], value: T): ArrayNonEmpty<T> {
const clone = array.slice();
clone.push(value);
return clone as ArrayNonEmpty<T>;
}
// Returns a copy of the array with the given value appended
export const append = `toSpliced` in Array.prototype ? _appendNew : _appendOld;
// Returns the concatenation of the two arrays, potentially reusing the input array if one of the arrays is empty
export function concat<T>(
first: readonly T[],
second: readonly T[]
): readonly T[] {
if (first.length === 0) return second;
if (second.length === 0) return first;
return first.concat(second);
}
export function _reverseNew<T>(
array: readonly T[],
start?: number,
end?: number
): T[] {
const source =
undefined !== start || undefined !== end
? array.slice(start ?? 0, (end ?? array.length - 1) + 1)
: array;
return source.toReversed();
}
export function _reverseOld<T>(
array: readonly T[],
start?: number,
end?: number
): T[] {
const _start = start ?? 0;
const _end = end ?? array.length - 1;
const length = _end - _start + 1;
const res = [] as T[];
let arrayIndex = _start - 1;
let resIndex = length - 1;
while (++arrayIndex <= _end) res[resIndex--] = array[arrayIndex];
return res;
}
// Returns an copy of the array between the start and end indices, with the elements in reversed order.
export const reverse =
'toReversed' in Array.prototype ? _reverseNew : _reverseOld;
// Performs given function on each element of the array, in reverse order if 'reversed' is true.
export function forEach<T>(
array: readonly T[],
f: (value: T, index: number, halt: () => void) => void,
state: TraverseState = TraverseState(),
reversed = false
): void {
if (state.halted) return;
const { halt } = state;
if (reversed) {
let i = array.length;
while (!state.halted && --i >= 0) {
f(array[i], state.nextIndex(), halt);
}
} else {
const length = array.length;
let i = -1;
while (!state.halted && ++i < length) {
f(array[i], state.nextIndex(), halt);
}
}
}
// Returns a copy of the array where given function is applied to each element
export function map<T, R>(
array: readonly T[],
f: (value: T, index: number) => R,
indexOffset = 0
): R[] {
if (indexOffset === 0) {
// without offset, can use standard array map
return array.map(f);
}
const result: R[] = [];
let index = indexOffset;
let i = -1;
const length = array.length;
while (++i < length) {
result[i] = f(array[i], index++);
}
return result;
}
// Returns a copy of the array where given functio is applied to each element in reverse order
export function reverseMap<T, R>(
array: readonly T[],
f: (value: T, index: number) => R,
indexOffset = 0
): R[] {
const result: R[] = [];
let index = indexOffset;
let arrayIndex = array.length;
let resultIndex = 0;
while (--arrayIndex >= 0)
result[resultIndex++] = f(array[arrayIndex], index++);
return result;
}
export function _prependNew<T>(
array: readonly T[],
value: T
): ArrayNonEmpty<T> {
return array.toSpliced(0, 0, value) as ArrayNonEmpty<T>;
}
export function _prependOld<T>(
array: readonly T[],
value: T
): ArrayNonEmpty<T> {
const clone = array.slice();
clone.unshift(value);
return clone as ArrayNonEmpty<T>;
}
// Returns a copy of the given array with the given value added at the start
export const prepend =
`toSpliced` in Array.prototype ? _prependNew : _prependOld;
export function _lastNew<T>(arr: readonly T[]): T {
return arr.at(-1)!;
}
export function _lastOld<T>(arr: readonly T[]): T {
return arr[arr.length - 1];
}
// Returns the last element of the array
export const last = `at` in Array.prototype ? _lastNew : _lastOld;
export function _updateNew<T>(
arr: readonly T[],
index: number,
updater: Update<T>
): readonly T[] {
if (index < 0 || index >= arr.length) {
return arr;
}
const curValue = arr[index];
const newValue = Update(curValue, updater);
if (Object.is(newValue, curValue)) {
return arr;
}
return arr.with(index, newValue);
}
export function _updateOld<T>(
arr: readonly T[],
index: number,
updater: Update<T>
): readonly T[] {
if (index < 0 || index >= arr.length) {
return arr;
}
const curValue = arr[index];
const newValue = Update(curValue, updater);
if (Object.is(newValue, curValue)) {
return arr;
}
const newArr = arr.slice();
newArr[index] = newValue;
return newArr;
}
// Returns a copy of the array where the element at given index is replaced by the given updater.
// If the new element is the same as the old element, the original array is returned
export const update = `with` in Array.prototype ? _updateNew : _updateOld;
export function _modNew<T>(
arr: readonly T[],
index: number,
f: (value: T) => T
): readonly T[] {
if (index < 0 || index >= arr.length) {
return arr;
}
const curValue = arr[index];
const newValue = f(curValue);
if (Object.is(newValue, curValue)) {
return arr;
}
return arr.with(index, newValue);
}
export function _modOld<T>(
arr: readonly T[],
index: number,
f: (value: T) => T
): readonly T[] {
if (index < 0 || index >= arr.length) {
return arr;
}
const curValue = arr[index];
const newValue = f(curValue);
if (Object.is(newValue, curValue)) {
return arr;
}
const newArr = arr.slice();
newArr[index] = newValue;
return newArr;
}
// Returns a copy of the array where the element at given index is replaced by applying given function.
// If the new element is the same as the old element, the original array is returned
export const mod = `with` in Array.prototype ? _modNew : _modOld;
export function _insertNew<T>(arr: readonly T[], index: number, value: T): T[] {
return arr.toSpliced(index, 0, value);
}
export function _insertOld<T>(arr: readonly T[], index: number, value: T): T[] {
const clone = arr.slice();
clone.splice(index, 0, value);
return clone;
}
// Returns a copy of the array where at given index the given value is inserted
export const insert = `toSpliced` in Array.prototype ? _insertNew : _insertOld;
// Returns a copy of the array, without its first element
export function tail<T>(arr: readonly T[]): T[] {
return arr.slice(1);
}
// Returns a copy of the array, without its last element
export function init<T>(arr: readonly T[]): T[] {
return arr.slice(0, arr.length - 1);
}
export function _spliceNew<T>(
arr: readonly T[],
start: number,
deleteCount: number,
...items: T[]
): T[] {
return arr.toSpliced(start, deleteCount, ...items);
}
export function _spliceOld<T>(
arr: readonly T[],
start: number,
deleteCount: number,
...items: T[]
): T[] {
const clone = arr.slice();
clone.splice(start, deleteCount, ...items);
return clone;
}
// Immutable version of the array .splice command, always returns a new array
export const splice = `toSpliced` in Array.prototype ? _spliceNew : _spliceOld;
// Returns a copy of the array, where its 'sparse' property is kept (sparse = not all indices have a value)
export function copySparse<T>(arr: readonly T[]): T[] {
const clone: T[] = [];
for (const key in arr) {
clone[key] = arr[key];
}
return clone;
}
// Returns a copy of the array with given function applied to each element, where its 'sparse' property is kept
// (sparse = not all indices have a value)
export function mapSparse<T, T2>(
arr: readonly T[],
f: (value: T, index: number) => T2
): T2[] {
const result: T2[] = Array(arr.length);
for (const key in arr) {
result[key] = f(arr[key], key as any);
}
return result;
}