feat(stdlib): add immutable array ops, deep set, and isEqual comparator
Adds arrays/{move,insert,swap,remove}, collections/set, and comparators/isEqual (NaN/Date/RegExp/Map/Set/cycle-safe), wired into the barrels.
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
export * from './cluster';
|
||||
export * from './first';
|
||||
export * from './groupBy';
|
||||
export * from './insert';
|
||||
export * from './last';
|
||||
export * from './move';
|
||||
export * from './partition';
|
||||
export * from './range';
|
||||
export * from './remove';
|
||||
export * from './sum';
|
||||
export * from './swap';
|
||||
export * from './toArray';
|
||||
export * from './unique';
|
||||
export * from './zip';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { insert } from '.';
|
||||
|
||||
describe('insert', () => {
|
||||
it('insert a single item', () => {
|
||||
expect(insert(['a', 'c'], 1, 'b')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('insert multiple items', () => {
|
||||
expect(insert(['a', 'd'], 1, 'b', 'c')).toEqual(['a', 'b', 'c', 'd']);
|
||||
});
|
||||
|
||||
it('prepend at index 0', () => {
|
||||
expect(insert(['b', 'c'], 0, 'a')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('append when the index is too large', () => {
|
||||
expect(insert(['a'], 99, 'b', 'c')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('clamp a negative index to 0', () => {
|
||||
expect(insert(['b'], -5, 'a')).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('never mutate the source', () => {
|
||||
const source = ['a', 'b'];
|
||||
insert(source, 1, 'x');
|
||||
expect(source).toEqual(['a', 'b']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @name insert
|
||||
* @category Arrays
|
||||
* @description Return a new array with `items` inserted at `index`. The index is
|
||||
* clamped into `[0, length]`, so a too-large index appends. Never mutates.
|
||||
*
|
||||
* @param {readonly T[]} array - The source array
|
||||
* @param {number} index - Position to insert at
|
||||
* @param {...T} items - Items to insert
|
||||
* @returns {T[]} A new array with the items inserted
|
||||
*
|
||||
* @example
|
||||
* insert(['a', 'c'], 1, 'b'); // ['a', 'b', 'c']
|
||||
* insert(['a'], 99, 'b', 'c'); // ['a', 'b', 'c']
|
||||
*
|
||||
* @since 0.0.10
|
||||
*/
|
||||
export function insert<T>(array: readonly T[], index: number, ...items: T[]): T[] {
|
||||
const result = array.slice();
|
||||
const target = Math.max(0, Math.min(index, result.length));
|
||||
result.splice(target, 0, ...items);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { move } from '.';
|
||||
|
||||
describe('move', () => {
|
||||
it('move an item forward', () => {
|
||||
expect(move(['a', 'b', 'c'], 0, 2)).toEqual(['b', 'c', 'a']);
|
||||
});
|
||||
|
||||
it('move an item backward', () => {
|
||||
expect(move(['a', 'b', 'c'], 2, 0)).toEqual(['c', 'a', 'b']);
|
||||
});
|
||||
|
||||
it('clamp the target index', () => {
|
||||
expect(move(['a', 'b', 'c'], 0, 99)).toEqual(['b', 'c', 'a']);
|
||||
});
|
||||
|
||||
it('return a copy unchanged for an out-of-range source', () => {
|
||||
const source = ['a', 'b'];
|
||||
const result = move(source, 5, 0);
|
||||
expect(result).toEqual(['a', 'b']);
|
||||
expect(result).not.toBe(source);
|
||||
});
|
||||
|
||||
it('never mutate the source', () => {
|
||||
const source = ['a', 'b', 'c'];
|
||||
move(source, 0, 2);
|
||||
expect(source).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name move
|
||||
* @category Arrays
|
||||
* @description Return a new array with the item at `from` moved to `to`. Out-of-range
|
||||
* `from` returns a shallow copy unchanged; `to` is clamped into range. Never mutates.
|
||||
*
|
||||
* @param {readonly T[]} array - The source array
|
||||
* @param {number} from - Index to move from
|
||||
* @param {number} to - Index to move to
|
||||
* @returns {T[]} A new array with the item moved
|
||||
*
|
||||
* @example
|
||||
* move(['a', 'b', 'c'], 0, 2); // ['b', 'c', 'a']
|
||||
*
|
||||
* @since 0.0.10
|
||||
*/
|
||||
export function move<T>(array: readonly T[], from: number, to: number): T[] {
|
||||
const result = array.slice();
|
||||
|
||||
if (from < 0 || from >= result.length)
|
||||
return result;
|
||||
|
||||
const item = result.splice(from, 1)[0] as T;
|
||||
const target = Math.max(0, Math.min(to, result.length));
|
||||
result.splice(target, 0, item);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { remove } from '.';
|
||||
|
||||
describe('remove', () => {
|
||||
it('remove an item by index', () => {
|
||||
expect(remove(['a', 'b', 'c'], 1)).toEqual(['a', 'c']);
|
||||
});
|
||||
|
||||
it('remove the first and last items', () => {
|
||||
expect(remove(['a', 'b', 'c'], 0)).toEqual(['b', 'c']);
|
||||
expect(remove(['a', 'b', 'c'], 2)).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('return a copy unchanged for an out-of-range index', () => {
|
||||
const source = ['a', 'b'];
|
||||
const result = remove(source, 9);
|
||||
expect(result).toEqual(['a', 'b']);
|
||||
expect(result).not.toBe(source);
|
||||
});
|
||||
|
||||
it('never mutate the source', () => {
|
||||
const source = ['a', 'b', 'c'];
|
||||
remove(source, 1);
|
||||
expect(source).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @name remove
|
||||
* @category Arrays
|
||||
* @description Return a new array with the item at `index` removed. Returns a shallow
|
||||
* copy unchanged when the index is out of range. Never mutates.
|
||||
*
|
||||
* @param {readonly T[]} array - The source array
|
||||
* @param {number} index - Index of the item to remove
|
||||
* @returns {T[]} A new array without the removed item
|
||||
*
|
||||
* @example
|
||||
* remove(['a', 'b', 'c'], 1); // ['a', 'c']
|
||||
*
|
||||
* @since 0.0.10
|
||||
*/
|
||||
export function remove<T>(array: readonly T[], index: number): T[] {
|
||||
const result = array.slice();
|
||||
|
||||
if (index < 0 || index >= result.length)
|
||||
return result;
|
||||
|
||||
result.splice(index, 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { swap } from '.';
|
||||
|
||||
describe('swap', () => {
|
||||
it('swap two items', () => {
|
||||
expect(swap(['a', 'b', 'c'], 0, 2)).toEqual(['c', 'b', 'a']);
|
||||
});
|
||||
|
||||
it('return a copy unchanged when indices are equal', () => {
|
||||
const source = ['a', 'b'];
|
||||
const result = swap(source, 1, 1);
|
||||
expect(result).toEqual(['a', 'b']);
|
||||
expect(result).not.toBe(source);
|
||||
});
|
||||
|
||||
it('return a copy unchanged for out-of-range indices', () => {
|
||||
expect(swap(['a', 'b'], 0, 9)).toEqual(['a', 'b']);
|
||||
expect(swap(['a', 'b'], -1, 1)).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('never mutate the source', () => {
|
||||
const source = ['a', 'b', 'c'];
|
||||
swap(source, 0, 2);
|
||||
expect(source).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @name swap
|
||||
* @category Arrays
|
||||
* @description Return a new array with the items at indices `a` and `b` swapped.
|
||||
* Returns a shallow copy unchanged when either index is out of range or equal.
|
||||
* Never mutates.
|
||||
*
|
||||
* @param {readonly T[]} array - The source array
|
||||
* @param {number} a - First index
|
||||
* @param {number} b - Second index
|
||||
* @returns {T[]} A new array with the two items swapped
|
||||
*
|
||||
* @example
|
||||
* swap(['a', 'b', 'c'], 0, 2); // ['c', 'b', 'a']
|
||||
*
|
||||
* @since 0.0.10
|
||||
*/
|
||||
export function swap<T>(array: readonly T[], a: number, b: number): T[] {
|
||||
const result = array.slice();
|
||||
|
||||
if (a < 0 || b < 0 || a >= result.length || b >= result.length || a === b)
|
||||
return result;
|
||||
|
||||
const temp = result[a] as T;
|
||||
result[a] = result[b] as T;
|
||||
result[b] = temp;
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user