type CollectionByKey = Record; type OrderDirection = 'asc' | 'desc'; type OrderCallback = (member: T) => unknown; export function buildCollectionByKey(collection: T[], key: keyof T) { return collection.reduce((byKey: CollectionByKey, member: T) => { byKey[member[key]] = member; return byKey; }, {}); } export function buildCollectionByCallback( collection: T[], callback: (member: T) => [K, R], ) { return collection.reduce((byKey: Record, member: T) => { const [key, value] = callback(member); byKey[key] = value; return byKey; }, {} as Record); } export function mapValues( byKey: CollectionByKey, callback: (member: M, key: string, index: number, originalByKey: CollectionByKey) => R, ): CollectionByKey { return Object.keys(byKey).reduce((newByKey: CollectionByKey, key, index) => { newByKey[key] = callback(byKey[key], key, index, byKey); return newByKey; }, {}); } export function mapTruthyValues( byKey: CollectionByKey, callback: (member: M, key: string, index: number, originalByKey: CollectionByKey) => R | Falsy, ): CollectionByKey { return Object.keys(byKey).reduce((newByKey: CollectionByKey, key, index) => { const value = callback(byKey[key], key, index, byKey); if (value) { newByKey[key] = value; } return newByKey; }, {}); } export function pick(object: T, keys: K[]) { return keys.reduce((result, key) => { result[key] = object[key]; return result; }, {} as Pick); } export function pickTruthy(object: T, keys: K[]) { return keys.reduce((result, key) => { if (object[key]) { result[key] = object[key]; } return result; }, {} as Pick); } export function omit(object: T, keys: K[]): Omit { const stringKeys = new Set(keys.map(String)); const savedKeys = Object.keys(object) .filter((key) => !stringKeys.has(key)) as Array>; return pick(object, savedKeys); } export function omitUndefined(object: T): T { return Object.keys(object).reduce((result, stringKey) => { const key = stringKey as keyof T; if (object[key] !== undefined) { result[key] = object[key]; } return result; }, {} as T); } export function orderBy( collection: T[], orderRule: (keyof T) | OrderCallback | ((keyof T) | OrderCallback)[], mode: OrderDirection | [OrderDirection, OrderDirection] = 'asc', ): T[] { function compareValues(a: T, b: T, currentOrderRule: (keyof T) | OrderCallback, isAsc: boolean) { const aValue = (typeof currentOrderRule === 'function' ? currentOrderRule(a) : a[currentOrderRule]) || 0; const bValue = (typeof currentOrderRule === 'function' ? currentOrderRule(b) : b[currentOrderRule]) || 0; // @ts-expect-error Rely on the JS to handle the comparison return isAsc ? aValue - bValue : bValue - aValue; } if (Array.isArray(orderRule)) { const [mode1, mode2] = Array.isArray(mode) ? mode : [mode, mode]; const [orderRule1, orderRule2] = orderRule; const isAsc1 = mode1 === 'asc'; const isAsc2 = mode2 === 'asc'; return collection.sort((a, b) => { return compareValues(a, b, orderRule1, isAsc1) || compareValues(a, b, orderRule2, isAsc2); }); } const isAsc = mode === 'asc'; return collection.sort((a, b) => { return compareValues(a, b, orderRule, isAsc); }); } export function unique(array: T[]): T[] { return Array.from(new Set(array)); } export function uniqueByField(array: T[], field: keyof T): T[] { return [...new Map(array.map((item) => [item[field], item])).values()]; } export function compact(array: (T | Falsy)[]): T[] { return array.filter(Boolean); } export function areSortedArraysEqual(array1: any[], array2: any[]) { if (array1.length !== array2.length) { return false; } return array1.every((item, i) => item === array2[i]); } export function areSortedArraysIntersecting(array1: any[], array2: any[]) { return array1[0] <= array2[array2.length - 1] && array1[array1.length - 1] >= array2[0]; } export function isInsideSortedArrayRange(value: any, array: any[]) { return array[0] <= value && value <= array[array.length - 1]; } export function findIntersectionWithSet(array: T[], set: Set): T[] { return array.filter((a) => set.has(a)); } /** * Exlude elements from base array. Both arrays should be sorted in same order * @param base * @param toExclude * @returns New array without excluded elements */ export function excludeSortedArray(base: T[], toExclude: T[]) { if (!base?.length) return base; const result: T[] = []; let excludeIndex = 0; for (let i = 0; i < base.length; i++) { if (toExclude[excludeIndex] === base[i]) { excludeIndex += 1; } else { result.push(base[i]); } } return result; } export function split(array: T[], chunkSize: number) { const result: T[][] = []; for (let i = 0; i < array.length; i += chunkSize) { result.push(array.slice(i, i + chunkSize)); } return result; } export function partition( array: T[], filter: (value: T, index: number, array: T[]) => boolean | undefined, ): [T[], T[]] { const pass: T[] = []; const fail: T[] = []; array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e)); return [pass, fail]; } export function cloneDeep(value: T): T { if (!isObject(value)) { return value; } if (Array.isArray(value)) { return value.map(cloneDeep) as typeof value; } return Object.keys(value).reduce((acc, key) => { acc[key as keyof T] = cloneDeep(value[key as keyof T]); return acc; }, {} as T); } export function isLiteralObject(value: any): value is AnyLiteral { return isObject(value) && !Array.isArray(value); } function isObject(value: any): value is object { // eslint-disable-next-line no-null/no-null return typeof value === 'object' && value !== null; } export function findLast(array: Array, predicate: (value: T, index: number, obj: T[]) => boolean): T | undefined { let cursor = array.length; while (cursor--) { if (predicate(array[cursor], cursor, array)) { return array[cursor]; } } return undefined; } export function compareFields(a: T, b: T) { return Number(b) - Number(a); }