UNPKG

@handy-common-utils/misc-utils

Version:
268 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.distributeRoundRobin = distributeRoundRobin; exports.downSampleRandomly = downSampleRandomly; exports.findInSorted = findInSorted; exports.findIndexInSorted = findIndexInSorted; exports.findInsertionIndexInSorted = findInsertionIndexInSorted; exports.findWithinRangeInSorted = findWithinRangeInSorted; exports.shuffle = shuffle; exports.chunk = chunk; exports.partition = partition; /** * Distributes an array into a number of groups in a round robin fashion. * This function has been tuned for performance. * @param array The input array * @param groups Number of groups the elements in the input array need to be distributed into. * @returns The result as an array of arrays which each represents a group */ function distributeRoundRobin(array, groups) { if (groups <= 0) { return []; } // eslint-disable-next-line unicorn/no-new-array const result = new Array(groups); let remainingElements = array.length; for (let groupIndex = 0, remainingGroups = groups; groupIndex < groups; groupIndex++, remainingGroups--) { const size = Math.ceil(remainingElements / remainingGroups); // eslint-disable-next-line unicorn/no-new-array const group = new Array(size); result[groupIndex] = group; for (let i = 0, j = groupIndex; i < size; i++, j += groups) { group[i] = array[j]; } remainingElements -= size; } return result; } // export function distributeRoundRobinWithoutOptimisation<T>(array: Array<T>, groups: number): Array<Array<T>> { // if (groups <= 0) { // return []; // } // const result: Array<Array<T>> = []; // for (let i = 0; i < groups; i++) { // result.push([]); // } // // eslint-disable-next-line unicorn/no-for-loop // for (let i = 0; i < array.length; i++) { // result[i % groups].push(array[i]); // } // return result; // } /** * Down samples the input array randomly. * @param array The input array * @param numSamples Number of samples to be taken from the input array. * If the number of samples is greater than or equal to the length of the input array, * the output array will contain all the elements in the input array. * @param probabilityTransformerFunction A function that turns a random number within [0, 1) to another number within [0, 1). * If not provided, the identity function F(x) = x will be used. * The probability of an element being selected from the input array is determined by this function. * @returns A new array with the down sampled elements from the input array. * The order of the elements in the output array is the same as the input array. */ function downSampleRandomly(array, numSamples, probabilityTransformerFunction = x => x) { if (numSamples <= 0) { return []; } if (numSamples >= array.length) { return [...array]; } const result = []; const indexes = new Set(); while (indexes.size < numSamples) { const r = Math.random(); indexes.add(Math.floor(probabilityTransformerFunction(r) * array.length)); } // eslint-disable-next-line unicorn/no-for-loop for (let i = 0; i < array.length; i++) { if (indexes.has(i)) { result.push(array[i]); } } return result; } /** * Finds an element in a sorted array using a golden ratio split (0.618) which statistically performs better than a "standard" binary search. * @param array The sorted input array. If it is not sorted, the result would be incorrect. * @param compareFn A function that returns: * - 0 if the element is the exact match. * - A negative number if the element comes before the target. * - A positive number if the element comes after the target. * @returns The found element or undefined if not found or the array is null/empty. */ function findInSorted(array, compareFn) { if (!array || array.length === 0) { return undefined; } const GOLDEN_RATIO = 0.6180339887; const search = (low, high) => { if (low > high) { return undefined; } const index = Math.floor(low + (high - low) * GOLDEN_RATIO); const item = array[index]; const comparison = compareFn(item); if (comparison === 0) { return item; } if (comparison < 0) { return search(index + 1, high); } return search(low, index - 1); }; return search(0, array.length - 1); } /** * Finds the index of an element in a sorted array using a golden ratio split (0.6180339887). * @param array The sorted input array. * @param compareFn A function that returns: * - 0 if the element is the exact match. * - A negative number if the element comes before the target. * - A positive number if the element comes after the target. * @returns The index of the found element, or undefined if not found or the array is null/empty. */ function findIndexInSorted(array, compareFn) { if (!array || array.length === 0) { return undefined; } const GOLDEN_RATIO = 0.6180339887; const search = (low, high) => { if (low > high) { return undefined; } const index = Math.floor(low + (high - low) * GOLDEN_RATIO); const item = array[index]; const comparison = compareFn(item); if (comparison === 0) { return index; } if (comparison < 0) { return search(index + 1, high); } return search(low, index - 1); }; return search(0, array.length - 1); } /** * Finds the index where an item should be inserted into a sorted array to maintain order, * using a golden ratio split (0.6180339887) for consistent performance. * @param array The sorted input array. * @param item The item to be inserted. Please note that it does not have to be of the same type as the elements in the array. * @param compareFn A function to compare the element in the array with the item passed in (standard comparator). * Should return a negative number if the element in the array is before the item passed in, * 0 if it is at the same position as the item passed in, and a positive number if the element in the array is after the item passed in. * @returns The insertion index. */ function findInsertionIndexInSorted(array, item, compareFn) { const GOLDEN_RATIO = 0.6180339887; let low = 0; let high = array.length; while (low < high) { const index = Math.floor(low + (high - low) * GOLDEN_RATIO); const mid = Math.max(low, Math.min(index, high - 1)); if (compareFn(array[mid], item) < 0) { low = mid + 1; } else { high = mid; } } return low; } /** * Finds all elements within a specified range in a sorted array. * @param array The sorted input array. * @param lowBoundary The lower boundary of the range. * @param highBoundary The upper boundary of the range. * @param compareFn A function to compare an element of type T with a boundary of type B. * Should return a negative number if `a < b`, 0 if `a === b`, and a positive number if `a > b`. * @param options specify whether boundaries are inclusive or exclusive. By default, both are inclusive. * @param options.lowInclusive optionally specifying whether the lowBoundary is inclusive. Default is true. * @param options.highInclusive optionally specifying whether the highBoundary is inclusive. Default is true. * @returns An array containing all elements within the specified range. */ function findWithinRangeInSorted(array, lowBoundary, highBoundary, compareFn, options) { var _a, _b; const lowInclusive = (_a = options === null || options === void 0 ? void 0 : options.lowInclusive) !== null && _a !== void 0 ? _a : true; const highInclusive = (_b = options === null || options === void 0 ? void 0 : options.highInclusive) !== null && _b !== void 0 ? _b : true; const startIndex = findInsertionIndexInSorted(array, lowBoundary, compareFn); const result = []; for (let i = startIndex; i < array.length; i++) { const item = array[i]; const lowCmp = compareFn(item, lowBoundary); // Check if item meets lower boundary constraint if (lowCmp < 0 || (!lowInclusive && lowCmp === 0)) { continue; } const highCmp = compareFn(item, highBoundary); // Check if item meets upper boundary constraint if (highCmp > 0 || (!highInclusive && highCmp === 0)) { break; } result.push(item); } return result; } /** * Shuffles the elements of an array randomly using the Fisher-Yates algorithm. * @param array The input array. * @returns A new array with the elements shuffled. */ function shuffle(array) { const result = [...array]; for (let i = result.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [result[i], result[j]] = [result[j], result[i]]; } return result; } /** * Splits an array into chunks of a specified size. * @param array The input array. * @param size The size of each chunk. * @returns An array of chunks. */ function chunk(array, size) { if (size <= 0) { return []; } const result = []; for (let i = 0; i < array.length; i += size) { result.push(array.slice(i, i + size)); } return result; } /** * Partitions an array into multiple groups based on a classifier function. * Please note that by default the returned array could have length of zero if the input array is empty, * or length of 1 if the classifier always returns the same value, or length of any number depending on the classifier function. * For your use case, you may want to specify `initialCapacity` to make sure that the returned array always contains the * specified number of elements or to avoid the overhead of resizing the result array. * @param array The input array. * @param classifier A function that returns a boolean or a non-negative integer. * - If boolean: true maps to group 0, false maps to group 1. * - If number: the index of the group. Negative numbers map to group 0. * @param initialCapacity The initial capacity of the result array. * If not specified, the returned array could contain any number of elements depending on the classifier function. * For example, setting it to 2 could be useful for most binary classification use case. * @returns An array of arrays, each representing a group. */ function partition(array, classifier, initialCapacity) { const result = []; if (initialCapacity) { for (let i = 0; i < initialCapacity; i++) { result.push([]); } } for (const item of array) { const key = classifier(item); const index = typeof key === 'boolean' ? (key ? 0 : 1) : (key < 0 ? 0 : Math.floor(key)); while (result.length <= index) { result.push([]); } result[index].push(item); } return result; } //# sourceMappingURL=array.js.map