UNPKG

@tsdotnet/array-utility

Version:

A small collection of useful array functions.

310 lines 9.85 kB
/*! * @author electricessence / https://github.com/electricessence/ * @license MIT */ import ArgumentOutOfRangeException from '@tsdotnet/exceptions/dist/ArgumentOutOfRangeException'; import ArgumentNullException from '@tsdotnet/exceptions/dist/ArgumentNullException'; import ArgumentException from '@tsdotnet/exceptions/dist/ArgumentException'; import init from '@tsdotnet/array-init'; import type from '@tsdotnet/type'; import copy, { arrayCopyTo as copyTo } from '@tsdotnet/array-copy'; import integer from '@tsdotnet/integer'; import areEqual from '@tsdotnet/compare/dist/areEqual'; export { init, copy, copyTo }; const CBN = 'Cannot be null.', CB0 = 'Cannot be zero.', CBL0 = 'Cannot be less than zero.', VFN = 'Must be a valid finite number'; /** * Checks to see where the provided array contains an item/value. * If the array value is null, then -1 is returned. * @param array * @param item * @param {function?} equalityComparer * @returns {number} */ export function indexOf(array, item, equalityComparer = areEqual) { const len = array && array.length; if (len) { // NaN NEVER evaluates its equality so be careful. if (equalityComparer === areEqual && array instanceof Array && !type.isTrueNaN(item)) return array.indexOf(item); for (let i = 0; i < len; i++) { // 'areEqual' includes NaN==NaN evaluation. if (equalityComparer(array[i], item)) return i; } } return -1; } /** * Checks to see if the provided array contains an item. * If the array value is null, then false is returned. * @param array * @param item * @param {function?} equalityComparer * @returns {boolean} */ export function contains(array, item, equalityComparer = areEqual) { return indexOf(array, item, equalityComparer) !== -1; } /** * Finds and replaces a value from an array. Will replaces all instances unless a maximum is specified. * @param array * @param old * @param newValue * @param max * @returns {number} */ export function replace(array, old, newValue, max = Infinity) { if (!array || !array.length || max === 0) return 0; if (max < 0) throw new ArgumentOutOfRangeException('max', max, CBL0); if (!max) max = Infinity; // just in case. let count = 0; for (let i = 0, len = array.length; i < len; i++) { if (array[i] === old) { array[i] = newValue; ++count; if (count === max) break; } } return count; } /** * Replaces values of an array across a range of indexes. * @param array * @param value * @param start * @param stop */ export function updateRange(array, value, start = 0, stop) { if (!array) return; integer.assertZeroOrGreater(start, 'start'); if (!stop && stop !== 0) stop = array.length; integer.assert(stop, 'stop'); if (stop < start) throw new ArgumentOutOfRangeException('stop', stop, 'is less than start'); for (let i = start; i < stop; i++) { array[i] = value; } } /** * Clears (sets to null) values of an array across a range of indexes. * @param array * @param start * @param stop */ export function clearEach(array, start = 0, stop) { updateRange(array, null, start, stop); } /** * Ensures a value exists within an array. If not found, adds to the end. * @param array * @param item * @param {function?} equalityComparer * @returns {boolean} */ export function register(array, item, equalityComparer = areEqual) { if (!array) throw new ArgumentNullException('array', CBN); const len = array.length; // avoid querying .length more than once. * const ok = !len || !contains(array, item, equalityComparer); if (ok) array[len] = item; // * push would query length again. return ok; } /** * Returns the first index of which the provided predicate returns true. * Returns -1 if always false. * @param array * @param predicate * @returns {number} */ export function findIndex(array, predicate) { if (!array) throw new ArgumentNullException('array', CBN); if (!type.isFunction(predicate)) throw new ArgumentException('predicate', 'Must be a function.'); const len = array.length; if (!type.isNumber(len, true) || len < 0) throw new ArgumentException('array', 'Does not have a valid length.'); if (array instanceof Array) { for (let i = 0; i < len; i++) { if (predicate(array[i], i)) return i; } } else { for (let i = 0; i < len; i++) { if (i in array && predicate(array[i], i)) return i; } } return -1; } /** * Allows for using "false" to cause forEach to break. * Can also be applied to a structure that indexes like an array, but may not be. * @param source * @param action */ export function forEach(source, action) { if (source && action != null) { // Don't cache the length since it is possible that the underlying array changed. for (let i = 0; i < source.length; i++) { // noinspection PointlessBooleanExpressionJS if (action(source[i], i) === false) break; } } } /** * Is similar to Array.map() but instead of returning a new array, it updates the existing indexes. * Can also be applied to a structure that indexes like an array, but may not be. * @param target * @param fn */ export function applyTo(target, fn) { if (target && fn != null) { for (let i = 0; i < target.length; i++) { target[i] = fn(target[i], i); } } } /** * Removes an entry at a specified index. * @param array * @param index * @returns {boolean} True if the value was able to be removed. */ export function removeIndex(array, index) { if (!array) throw new ArgumentNullException('array', CBN); integer.assert(index, 'index'); if (index < 0) throw new ArgumentOutOfRangeException('index', index, CBL0); const exists = index < array.length; if (exists) array.splice(index, 1); return exists; } /** * Finds and removes a value from an array. Will remove all instances unless a maximum is specified. * @param array * @param value * @param max * @param {function?} equalityComparer * @returns {number} The number of times the value was found and removed. */ export function remove(array, value, max = Infinity, equalityComparer = areEqual) { if (!array || !array.length || max === 0) return 0; if (max < 0) throw new ArgumentOutOfRangeException('max', max, CBL0); let count = 0; if (!max || !isFinite(max)) { // Don't track the indexes and remove in reverse. for (let i = array.length - 1; i >= 0; i--) { if (equalityComparer(array[i], value)) { array.splice(i, 1); ++count; } } } else { // Since the user will expect it to happen in forward order... const found = []; // indexes; for (let i = 0, len = array.length; i < len; i++) { if (equalityComparer(array[i], value)) { found.push(i); ++count; if (count === max) break; } } for (let i = found.length - 1; i >= 0; i--) { array.splice(found[i], 1); } } return count; } /** * Simply repeats a value the number of times specified. * @param element * @param count * @returns {T[]} */ export function repeat(element, count) { integer.assert(count, 'count'); if (count < 0) throw new ArgumentOutOfRangeException('count', count, CBL0); const result = init(count); for (let i = 0; i < count; i++) { result[i] = element; } return result; } /** * Returns a range of numbers based upon the first value and the step value. * @param first * @param count * @param step * @returns {number[]} */ export function range(first, count, step = 1) { if (isNaN(first) || !isFinite(first)) throw new ArgumentOutOfRangeException('first', first, VFN); if (isNaN(count) || !isFinite(count)) throw new ArgumentOutOfRangeException('count', count, VFN); if (count < 0) throw new ArgumentOutOfRangeException('count', count, CBL0); const result = init(count); for (let i = 0; i < count; i++) { result[i] = first; first += step; } return result; } /** * Returns a range of numbers based upon the first value and the step value excluding any numbers at or beyond the until value. * @param first * @param until * @param step * @returns {number[]} */ export function rangeUntil(first, until, step = 1) { if (step === 0) throw new ArgumentOutOfRangeException('step', step, CB0); return range(first, (until - first) / step, step); } export function distinct(source) { if (!source) return []; // Allowing for null facilitates regex filtering. const seen = {}; return source.filter((e) => !(e in seen) && (seen[e] = true)); } /** * Takes any arrays within an array and inserts the values contained within in place of that array. * For every count higher than 0 in recurseDepth it will attempt an additional pass. Passing Infinity will flatten all arrays contained. * @param a * @param recurseDepth * @returns {any[]} */ export function flatten(a, recurseDepth = 0) { const result = []; for (let x of a) { if (x instanceof Array) { if (recurseDepth > 0) x = flatten(x, recurseDepth - 1); for (const e of x) result.push(e); } else result.push(x); } return result; } //# sourceMappingURL=arrayUtility.js.map