@kitiumai/utils-ts
Version:
Comprehensive TypeScript utilities for KitiumAI projects
607 lines • 16 kB
JavaScript
/**
* Array utility functions
*/
import { createUtilsError } from './error.js';
import { err, ok } from './result.js';
const normalizeChunkOptions = (sizeOrOptions) => {
if (typeof sizeOrOptions === 'number') {
return { size: sizeOrOptions, onError: 'throw' };
}
return {
onError: 'throw',
...sizeOrOptions,
};
};
const handleChunkError = (options, cause) => {
const error = createUtilsError({
code: 'INVALID_CHUNK_SIZE',
message: options.label
? `Chunk size must be greater than 0 for ${options.label}`
: 'Chunk size must be greater than 0',
details: {
size: options.size,
label: options.label,
},
cause,
});
if (options.onError === 'return') {
return err(error);
}
throw error;
};
export function chunk(arrayOrOptions, sizeOrOptions) {
if (!Array.isArray(arrayOrOptions)) {
const normalized = normalizeChunkOptions(arrayOrOptions);
return (array) => chunk(array, normalized);
}
const array = arrayOrOptions;
const options = normalizeChunkOptions(sizeOrOptions);
if (typeof options.size !== 'number' || Number.isNaN(options.size)) {
return handleChunkError({ ...options, size: Number.isNaN(options.size) ? NaN : options.size }, new Error('Size must be a valid number'));
}
if (options.size <= 0) {
return handleChunkError(options, new Error('Size must be positive'));
}
const result = [];
for (let i = 0; i < array.length; i += options.size) {
result.push(array.slice(i, i + options.size));
}
return options.onError === 'return' ? ok(result) : result;
}
const normalizeGroupByOptions = (selectorOrOptions) => {
if (typeof selectorOrOptions === 'function' ||
typeof selectorOrOptions === 'string' ||
typeof selectorOrOptions === 'number') {
return { selector: selectorOrOptions, allowUndefined: false, onError: 'throw' };
}
return {
allowUndefined: false,
onError: 'throw',
...selectorOrOptions,
};
};
export function groupBy(arrayOrSelector, selectorOrOptions) {
if (!Array.isArray(arrayOrSelector)) {
const normalized = normalizeGroupByOptions(arrayOrSelector);
return (array) => groupBy(array, normalized);
}
const array = arrayOrSelector;
const options = normalizeGroupByOptions(selectorOrOptions);
const result = {};
if (options.selector === undefined) {
const error = createUtilsError({
code: 'GROUP_BY_KEY_MISSING',
message: 'groupBy requires a selector or property key',
details: {},
});
if (options.onError === 'return') {
return err(error);
}
throw error;
}
for (const item of array) {
const keyOrValue = options.selector;
const key = typeof keyOrValue === 'function' ? keyOrValue(item) : item[keyOrValue];
if ((key === undefined || key === null) && !options.allowUndefined) {
const error = createUtilsError({
code: 'GROUP_BY_KEY_MISSING',
message: 'groupBy selector returned an undefined key',
details: { item },
});
if (options.onError === 'return') {
return err(error);
}
throw error;
}
const keyAsString = String(key);
if (!result[keyAsString]) {
result[keyAsString] = [];
}
result[keyAsString].push(item);
}
return options.onError === 'return' ? ok(result) : result;
}
/**
* Remove duplicate values from array
*
* @template T - The type of array elements
* @param array - The array to deduplicate
* @returns A new array with unique values
*
* @example
* ```ts
* unique([1, 2, 2, 3, 3, 3]) // [1, 2, 3]
* ```
*/
export function unique(array) {
return Array.from(new Set(array));
}
/**
* Remove duplicate values by a specific key
*
* @template T - The type of array elements
* @param array - The array to deduplicate
* @param key - The key to use for uniqueness comparison
* @returns A new array with unique values based on the key
*
* @example
* ```ts
* uniqueBy([{ id: 1, name: 'a' }, { id: 1, name: 'b' }], 'id')
* // [{ id: 1, name: 'a' }]
* ```
*/
export function uniqueBy(array, key) {
const seen = new Set();
return array.filter((item) => {
const value = item[key];
if (seen.has(value)) {
return false;
}
seen.add(value);
return true;
});
}
/**
* Split array into two arrays based on predicate
*
* @template T - The type of array elements
* @param array - The array to partition
* @param predicate - Function that returns true for items in the first array
* @returns A tuple of [passed, failed] arrays
*
* @example
* ```ts
* partition([1, 2, 3, 4, 5], x => x % 2 === 0) // [[2, 4], [1, 3, 5]]
* ```
*/
export function partition(array, predicate) {
const passed = [];
const failed = [];
array.forEach((item, index) => {
if (predicate(item, index)) {
passed.push(item);
}
else {
failed.push(item);
}
});
return [passed, failed];
}
/**
* Find common elements across all arrays
*
* @template T - The type of array elements
* @param arrays - Arrays to find intersection of
* @returns A new array with elements present in all arrays
*
* @example
* ```ts
* intersection([1, 2, 3], [2, 3, 4], [3, 4, 5]) // [3]
* ```
*/
export function intersection(...arrays) {
if (arrays.length === 0) {
return [];
}
if (arrays.length === 1) {
return unique(arrays[0] || []);
}
const [first, ...rest] = arrays;
if (!first) {
return [];
}
const set = new Set(first);
for (const arr of rest) {
const arrSet = new Set(arr);
for (const item of set) {
if (!arrSet.has(item)) {
set.delete(item);
}
}
}
return Array.from(set);
}
/**
* Find elements in first array that are not in second array
*
* @template T - The type of array elements
* @param a - The first array
* @param b - The second array
* @returns A new array with elements from a that are not in b
*
* @example
* ```ts
* difference([1, 2, 3, 4], [2, 4]) // [1, 3]
* ```
*/
export function difference(a, b) {
const bSet = new Set(b);
return a.filter((item) => !bSet.has(item));
}
/**
* Flatten nested array one level
*
* @template T - The type of array elements
* @param array - The array of arrays to flatten
* @returns A flattened array
*
* @example
* ```ts
* flatten([[1, 2], [3, 4]]) // [1, 2, 3, 4]
* ```
*/
export function flatten(array) {
return array.flat();
}
/**
* Flatten nested array recursively
*
* @template T - The type of array elements
* @param array - The array to flatten
* @returns A deeply flattened array
*
* @example
* ```ts
* flattenDeep([1, [2, [3, [4]]]]) // [1, 2, 3, 4]
* ```
*/
export function flattenDeep(array) {
return array.flat(Infinity);
}
/**
* Create array of numbers from start to end
*
* @param start - Start value (inclusive)
* @param end - End value (exclusive)
* @param step - Step size (default: 1)
* @returns An array of numbers
*
* @example
* ```ts
* range(0, 5) // [0, 1, 2, 3, 4]
* range(0, 10, 2) // [0, 2, 4, 6, 8]
* ```
*/
export function range(start, end, step = 1) {
const result = [];
for (let i = start; i < end; i += step) {
result.push(i);
}
return result;
}
/**
* Shuffle array randomly using Fisher-Yates algorithm
*
* @template T - The type of array elements
* @param array - The array to shuffle
* @returns A new shuffled array
*
* @example
* ```ts
* shuffle([1, 2, 3, 4, 5]) // [3, 1, 5, 2, 4] (random order)
* ```
*/
export function shuffle(array) {
const result = [...array];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
// Non-null assertions are safe here because we're within array bounds
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
/**
* Get random sample from array
*
* @template T - The type of array elements
* @param array - The array to sample from
* @param count - Number of items to sample (default: 1)
* @returns A new array with random samples
*
* @example
* ```ts
* sample([1, 2, 3, 4, 5], 2) // [3, 1] (random)
* ```
*/
export function sample(array, count = 1) {
const shuffled = shuffle(array);
return shuffled.slice(0, Math.min(count, array.length));
}
/**
* Check if arrays are equal (shallow comparison)
*
* @template T - The type of array elements
* @param a - First array
* @param b - Second array
* @returns True if arrays have the same length and elements
*
* @example
* ```ts
* arraysEqual([1, 2, 3], [1, 2, 3]) // true
* arraysEqual([1, 2], [1, 2, 3]) // false
* ```
*/
export function arraysEqual(a, b) {
if (a.length !== b.length) {
return false;
}
return a.every((item, index) => item === b[index]);
}
/**
* Remove falsy values from array
*
* @template T - The type of array elements
* @param array - The array to compact
* @returns A new array with falsy values removed
*
* @example
* ```ts
* compact([0, 1, false, 2, '', 3]) // [1, 2, 3]
* ```
*/
export function compact(array) {
return array.filter((item) => Boolean(item));
}
/**
* Get the first n elements of an array
*
* @template T - The type of array elements
* @param array - The array to take from
* @param n - Number of elements to take
* @returns A new array with the first n elements
*
* @example
* ```ts
* take([1, 2, 3, 4, 5], 3) // [1, 2, 3]
* ```
*/
export function take(array, n) {
return array.slice(0, n);
}
/**
* Get the last n elements of an array
*
* @template T - The type of array elements
* @param array - The array to take from
* @param n - Number of elements to take
* @returns A new array with the last n elements
*
* @example
* ```ts
* takeRight([1, 2, 3, 4, 5], 3) // [3, 4, 5]
* ```
*/
export function takeRight(array, n) {
return array.slice(-n);
}
/**
* Remove the first n elements from an array
*
* @template T - The type of array elements
* @param array - The array to drop from
* @param n - Number of elements to drop
* @returns A new array with the first n elements removed
*
* @example
* ```ts
* drop([1, 2, 3, 4, 5], 2) // [3, 4, 5]
* ```
*/
export function drop(array, n) {
return array.slice(n);
}
/**
* Remove the last n elements from an array
*
* @template T - The type of array elements
* @param array - The array to drop from
* @param n - Number of elements to drop
* @returns A new array with the last n elements removed
*
* @example
* ```ts
* dropRight([1, 2, 3, 4, 5], 2) // [1, 2, 3]
* ```
*/
export function dropRight(array, n) {
return array.slice(0, -n);
}
/**
* Combine two arrays into pairs
*
* @template T - The type of first array elements
* @template U - The type of second array elements
* @param array1 - First array
* @param array2 - Second array
* @returns An array of pairs
*
* @example
* ```ts
* zip([1, 2, 3], ['a', 'b', 'c']) // [[1, 'a'], [2, 'b'], [3, 'c']]
* ```
*/
export function zip(array1, array2) {
const length = Math.min(array1.length, array2.length);
const result = [];
for (let i = 0; i < length; i++) {
result.push([array1[i], array2[i]]);
}
return result;
}
/**
* Separate pairs into two arrays
*
* @template T - The type of first element in pairs
* @template U - The type of second element in pairs
* @param pairs - Array of pairs
* @returns A tuple of two arrays
*
* @example
* ```ts
* unzip([[1, 'a'], [2, 'b']]) // [[1, 2], ['a', 'b']]
* ```
*/
export function unzip(pairs) {
const result = [[], []];
for (const [first, second] of pairs) {
result[0].push(first);
result[1].push(second);
}
return result;
}
/**
* Get the first element of an array
*
* @template T - The type of array elements
* @param array - The array
* @returns The first element, or undefined if array is empty
*
* @example
* ```ts
* head([1, 2, 3]) // 1
* head([]) // undefined
* ```
*/
export function head(array) {
return array[0];
}
/**
* Get all elements except the first
*
* @template T - The type of array elements
* @param array - The array
* @returns A new array without the first element
*
* @example
* ```ts
* tail([1, 2, 3, 4]) // [2, 3, 4]
* ```
*/
export function tail(array) {
return array.slice(1);
}
/**
* Get the last element of an array
*
* @template T - The type of array elements
* @param array - The array
* @returns The last element, or undefined if array is empty
*
* @example
* ```ts
* last([1, 2, 3]) // 3
* last([]) // undefined
* ```
*/
export function last(array) {
return array[array.length - 1];
}
/**
* Get all elements except the last
*
* @template T - The type of array elements
* @param array - The array
* @returns A new array without the last element
*
* @example
* ```ts
* initial([1, 2, 3, 4]) // [1, 2, 3]
* ```
*/
export function initial(array) {
return array.slice(0, -1);
}
/**
* Combine multiple arrays and remove duplicates
*
* @template T - The type of array elements
* @param arrays - Arrays to combine
* @returns A new array with unique elements from all arrays
*
* @example
* ```ts
* union([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]
* ```
*/
export function union(...arrays) {
return unique(arrays.flat());
}
/**
* Remove specified values from an array
*
* @template T - The type of array elements
* @param array - The array to filter
* @param values - Values to remove
* @returns A new array without the specified values
*
* @example
* ```ts
* without([1, 2, 3, 4, 5], 2, 4) // [1, 3, 5]
* ```
*/
export function without(array, ...values) {
const excludeSet = new Set(values);
return array.filter((item) => !excludeSet.has(item));
}
/**
* Map and flatten in one operation
*
* @template T - The type of input array elements
* @template R - The type of output array elements
* @param array - The array to map
* @param fn - Function that returns a value or array
* @returns A flattened array of mapped values
*
* @example
* ```ts
* flatMap([1, 2, 3], x => [x, x * 2]) // [1, 2, 2, 4, 3, 6]
* ```
*/
export function flatMap(array, fn) {
return array.flatMap(fn);
}
/**
* Count occurrences of items by a key or function
*
* @template T - The type of array elements
* @param array - The array to count
* @param keyOrFn - A key of T or a function that returns a grouping key
* @returns An object with keys as grouping keys and values as counts
*
* @example
* ```ts
* countBy([{ type: 'a' }, { type: 'b' }, { type: 'a' }], 'type')
* // { a: 2, b: 1 }
* ```
*/
export function countBy(array, keyOrFn) {
const result = {};
for (const item of array) {
const key = typeof keyOrFn === 'function' ? String(keyOrFn(item)) : String(item[keyOrFn]);
result[key] = (result[key] || 0) + 1;
}
return result;
}
/**
* Create an object from an array using a key
*
* @template T - The type of array elements
* @param array - The array to convert
* @param keyOrFn - A key of T or a function that returns a key
* @returns An object with keys from the key function and values from the array
*
* @example
* ```ts
* keyBy([{ id: 1, name: 'a' }, { id: 2, name: 'b' }], 'id')
* // { 1: { id: 1, name: 'a' }, 2: { id: 2, name: 'b' } }
* ```
*/
export function keyBy(array, keyOrFn) {
const result = {};
for (const item of array) {
const key = typeof keyOrFn === 'function' ? String(keyOrFn(item)) : String(item[keyOrFn]);
result[key] = item;
}
return result;
}
//# sourceMappingURL=array.js.map