UNPKG

@barchart/common-js

Version:
537 lines (440 loc) 11.9 kB
import * as assert from './assert.js'; import * as is from './is.js'; /** * Utilities for working with arrays. * * @public * @module lang/array */ /** * Returns the unique items from an array, where the unique * key is determined via a strict equality check. * * @static * @param {Array} a * @returns {Array} */ export function unique(a) { assert.argumentIsArray(a, 'a'); return uniqueBy(a, item => item); } /** * Returns the unique items from an array, where the unique * key is determined by a delegate. * * @static * @param {Array} a * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function uniqueBy(a, keySelector) { assert.argumentIsArray(a, 'a'); return a.filter((item, index, array) => { const key = keySelector(item); return array.findIndex(candidate => key === keySelector(candidate)) === index; }); } /** * Splits array into groups and returns an object (where the properties have * arrays). Unlike the indexBy function, there can be many items which share * the same key. * * @static * @param {Array} a * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {object} */ export function groupBy(a, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(keySelector, 'keySelector', Function); return a.reduce((groups, item) => { const key = keySelector(item); if (!Object.prototype.hasOwnProperty.call(groups, key)) { groups[key] = [ ]; } groups[key].push(item); return groups; }, { }); } /** * Splits array into groups and returns an array of arrays where the items of each * nested array share a common key. * * @static * @param {Array} a * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function batchBy(a, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(keySelector, 'keySelector', Function); let currentKey = null; let currentBatch = null; return a.reduce((batches, item) => { const key = keySelector(item); if (currentBatch === null || currentKey !== key) { currentKey = key; currentBatch = []; batches.push(currentBatch); } currentBatch.push(item); return batches; }, [ ]); } /** * Splits array into groups and returns an object (where the properties are items from the * original array). Unlike the {@link array#groupBy} function, only one item can have a * given key value. * * @static * @param {Array} a * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {object} */ export function indexBy(a, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(keySelector, 'keySelector', Function); return a.reduce((map, item) => { const key = keySelector(item); if (Object.prototype.hasOwnProperty.call(map, key)) { throw new Error('Unable to index array. A duplicate key exists.'); } map[key] = item; return map; }, { }); } /** * Returns a new array containing all but the first item. * * @static * @param {Array} a * @returns {Array} */ export function dropLeft(a) { assert.argumentIsArray(a, 'a'); let returnRef = Array.from(a); if (returnRef.length !== 0) { returnRef.shift(); } return returnRef; } /** * Returns a new array containing all but the last item. * * @static * @param {Array} a * @returns {Array} */ export function dropRight(a) { assert.argumentIsArray(a, 'a'); let returnRef = Array.from(a); if (returnRef.length !== 0) { returnRef.pop(); } return returnRef; } /** * Returns the first item from an array, or an undefined value, if the * array is empty. * * @static * @param {Array} a * @returns {*|undefined} */ export function first(a) { assert.argumentIsArray(a, 'a'); let returnRef; if (a.length !== 0) { returnRef = a[0]; } else { returnRef = undefined; } return returnRef; } /** * Returns the last item from an array, or an undefined value, if the * array is empty. * * @static * @param {Array} a * @returns {*|undefined} */ export function last(a) { assert.argumentIsArray(a, 'a'); let returnRef; if (a.length !== 0) { returnRef = a[a.length - 1]; } else { returnRef = undefined; } return returnRef; } /** * Returns a copy of an array, replacing any item that is itself an array * with the item's items. * * @static * @param {Array} a * @param {boolean=} recursive - If true, all nested arrays will be flattened. * @returns {Array} */ export function flatten(a, recursive) { assert.argumentIsArray(a, 'a'); assert.argumentIsOptional(recursive, 'recursive', Boolean); const empty = []; let flat = empty.concat.apply(empty, a); if (recursive && flat.some(x => is.array(x))) { flat = flatten(flat, true); } return flat; } /** * Breaks an array into smaller arrays, returning an array of arrays. * * @static * @param {Array} a * @param {number} size - The maximum number of items per partition. * @returns {Array<Array>} */ export function partition(a, size) { assert.argumentIsArray(a, 'a'); assert.argumentIsOptional(size, 'size', Number); const copy = a.slice(0); const partitions = [ ]; while (copy.length !== 0) { partitions.push(copy.splice(0, size)); } return partitions; } /** * Set difference operation, returning any item in "a" that is not * contained in "b" (using strict equality). * * @static * @param {Array} a * @param {Array} b * @returns {Array} */ export function difference(a, b) { return differenceBy(a, b, item => item); } /** * Set difference operation, returning any item in "a" that is not * contained in "b" (where the uniqueness is determined by a delegate). * * @static * @param {Array} a * @param {Array} b * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function differenceBy(a, b, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsArray(b, 'b'); assert.argumentIsRequired(keySelector, 'keySelector', Function); const returnRef = [ ]; a.forEach((candidate) => { const candidateKey = keySelector(candidate); const exclude = b.some(comparison => candidateKey === keySelector(comparison)); if (!exclude) { returnRef.push(candidate); } }); return returnRef; } /** * Set symmetric difference operation (using strict equality). In * other words, this is the union of the differences between the * sets. * * @static * @param {Array} a * @param {Array} b * @returns {Array} */ export function differenceSymmetric(a, b) { return differenceSymmetricBy(a, b, item => item); } /** * Set symmetric difference operation, where the uniqueness is determined by a delegate. * * @static * @param {Array} a * @param {Array} b * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function differenceSymmetricBy(a, b, keySelector) { return unionBy(differenceBy(a, b, keySelector), differenceBy(b, a, keySelector), keySelector); } /** * Set union operation (using strict equality). * * @static * @param {Array} a * @param {Array} b * @returns {Array} */ export function union(a, b) { return unionBy(a, b, item => item); } /** * Set union operation, where the uniqueness is determined by a delegate. * * @static * @param {Array} a * @param {Array} b * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function unionBy(a, b, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsArray(b, 'b'); assert.argumentIsRequired(keySelector, 'keySelector', Function); const returnRef = a.slice(); b.forEach((candidate) => { const candidateKey = keySelector(candidate); const exclude = returnRef.some(comparison => candidateKey === keySelector(comparison)); if (!exclude) { returnRef.push(candidate); } }); return returnRef; } /** * Set intersection operation (using strict equality). * * @static * @param {Array} a * @param {Array} b * @returns {Array} */ export function intersection(a, b) { return intersectionBy(a, b, item => item); } /** * Set intersection operation, where the uniqueness is determined by a delegate. * * @static * @param {Array} a * @param {Array} b * @param {Function} keySelector - A function that returns a unique key for an item. * @returns {Array} */ export function intersectionBy(a, b, keySelector) { assert.argumentIsArray(a, 'a'); assert.argumentIsArray(b, 'b'); const returnRef = [ ]; a.forEach((candidate) => { const candidateKey = keySelector(candidate); const include = b.some(comparison => candidateKey === keySelector(comparison)); if (include) { returnRef.push(candidate); } }); return returnRef; } /** * Removes the first item from an array which matches a predicate. * * @static * @public * @template T * @param {T[]} a * @param {(value: T, index: number, array: T[]) => boolean} predicate * @returns {boolean} */ export function remove(a, predicate) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(predicate, 'predicate', Function); const index = a.findIndex(predicate); const found = !(index < 0); if (found) { a.splice(index, 1); } return found; } /** * Inserts an item into an array using a binary search is used to determine the * proper point for insertion and returns the same array. * * @static * @public * @param {Array} a * @param {*} item * @param {(a: any, b: any) => number} comparator * @returns {Array} */ export function insert(a, item, comparator) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(comparator, 'comparator', Function); if (a.length === 0 || !(comparator(item, a[a.length - 1]) < 0)) { a.push(item); } else if (comparator(item, a[0]) < 0) { a.unshift(item); } else { a.splice(binarySearchForInsert(a, item, comparator, 0, a.length - 1), 0, item); } return a; } /** * Performs a binary search to locate an item within an array. * * @param {*[]} a * @param {*} key * @param {Function} comparator * @param {number=} start * @param {number=} end * @returns {*|null} */ export function binarySearch(a, key, comparator, start, end) { assert.argumentIsArray(a, 'a'); assert.argumentIsRequired(comparator, 'comparator', Function); assert.argumentIsOptional(start, 'start', Number); assert.argumentIsOptional(end, 'end', Number); if (a.length === 0) { return null; } return binarySearchForMatch(a, key, comparator, start || 0, end || a.length - 1); } function binarySearchForMatch(a, key, comparator, start, end) { const size = end - start; const midpointIndex = start + Math.floor(size / 2); const midpointItem = a[ midpointIndex ]; const comparison = comparator(key, midpointItem); if (comparison === 0) { return midpointItem; } else if (size < 2) { const finalIndex = a.length - 1; const finalItem = a[ finalIndex ]; if (end === finalIndex && comparator(key, finalItem) === 0) { return finalItem; } else { return null; } } else if (comparison > 0) { return binarySearchForMatch(a, key, comparator, midpointIndex, end); } else { return binarySearchForMatch(a, key, comparator, start, midpointIndex); } } function binarySearchForInsert(a, item, comparator, start, end) { const size = end - start; const midpointIndex = start + Math.floor(size / 2); const midpointItem = a[midpointIndex]; const comparison = comparator(item, midpointItem); if (size < 2) { if (comparison > 0) { const finalIndex = a.length - 1; if (end === finalIndex && comparator(item, a[finalIndex]) > 0) { return end + 1; } return end; } return start; } if (comparison > 0) { return binarySearchForInsert(a, item, comparator, midpointIndex, end); } return binarySearchForInsert(a, item, comparator, start, midpointIndex); }