UNPKG

space-lift

Version:

TypeScript Array, Object, Map, Set, Union, Enum utils

277 lines (276 loc) 8.36 kB
import { identity } from './function'; import { getValue } from './wrapper'; import { MapWrapper } from './map'; /** An Array wrapper providing extra functionalities and more chaining opportunities */ export class ArrayWrapper { constructor(_value) { this._value = _value; this._isLiftWrapper = true; /** * Pipes this Array with an arbitrary transformation function. */ this.pipe = pipe; } value() { return this._value; } _clone() { return this._value.slice(); } /** * Appends one item at the end of the Array. */ append(item) { return new ArrayWrapper([...this._value, item]); } /** * Appends an Iterable of items at the end of the Array. */ appendAll(items) { return new ArrayWrapper([...this._value, ...items]); } /** * Filters all the falsy elements out of this Array. * All occurences of false, null, undefined, 0, "" will be removed. */ compact() { return this.filter(x => !!x); } /** * Counts the items satisfying a predicate. */ count(predicate) { return this.pipe(arr => arr.reduce((count, item, index) => count + (predicate(item, index) ? 1 : 0), 0)); } /** * Maps this Array's items, unless void or undefined is returned, in which case the item is filtered. * This is effectively a `filter` + `map` combined in one. */ collect(iterator) { const result = []; this._value.forEach((item, index) => { const res = iterator(item, index); if (res !== undefined) result.push(res); }); return new ArrayWrapper(result); } /** * Creates an array without any duplicate item. * If a key function is passed, items will be compared based on the result of that function; * if not, they will be compared using strict equality. */ distinct(getKey) { const items = new Set(); return this.collect((item, index) => { const key = getKey ? getKey(item, index) : item; if (items.has(key)) return; items.add(key); return item; }); } /** * Drops the first 'count' items from this Array. */ drop(count) { return this.pipe(arr => arr.slice(count)); } /** * Drops the last 'count' items from this Array. */ dropRight(count) { return this.pipe(arr => arr.slice(0, -count)); } filter(predicate) { return this.pipe(arr => arr.filter(predicate)); } /** * Returns the first element in this Array or undefined. */ first() { return this._value[0]; } /** * Maps this Array to an Array of Array | ArrayWrapper using a mapper function then flattens it. */ flatMap(fun) { const arr = this._value, result = []; for (let i = 0; i < arr.length; i++) { result.push.apply(result, getValue(fun(arr[i], i))); } return new ArrayWrapper(result); } flatten() { const arr = this.value(), result = []; for (let i = 0; i < arr.length; i++) { const item = arr[i]; result.push.apply(result, item); } return new ArrayWrapper(result); } /** * Reduces this Array into a single value, using a starting value. */ reduce(startValue, func) { return this.pipe(arr => arr.reduce(func, startValue)); } /** * Returns the item found at the provided index or undefined. */ get(index) { return this._value[index]; } /** * Creates a Map where keys are the results of running each element through a discriminator function. * The corresponding value of each key is an array of the elements responsible for generating the key. */ groupBy(discriminator) { const groups = new Map(); this._value.forEach((item, index) => { const key = discriminator(item, index); if (!groups.has(key)) groups.set(key, []); groups.get(key).push(item); }); return new MapWrapper(groups); } /** * Creates a new Array where each sub array contains at most 'bySize' elements. */ grouped(bySize) { return this.pipe(arr => { const result = []; let current = []; for (let i = 0; i < arr.length; i++) { if (current.length < bySize) { current.push(arr[i]); } if (current.length === bySize || i === arr.length - 1) { result.push(current); current = []; } } return result; }); } /** * Inserts an item at a specified index. */ insert(index, item) { return this.pipe(arr => { const result = arr.slice(); result.splice(index, 0, item); return result; }); } /** * Returns the item found at the last index or undefined. */ last() { return this._value[this._value.length - 1]; } /** * Maps this Array using a mapper function. */ map(fun) { return this.pipe(arr => arr.map(fun)); } /** * Removes the item found at the specified index. */ removeAt(index) { const result = this._clone(); result.splice(index, index < 0 ? 0 : 1); return new ArrayWrapper(result); } /** * Reverses the Array. */ reverse() { return this.pipe(arr => arr.slice().reverse()); } /** * Sorts the Array in ascending order, using one or more iterators specifying which field to compare. * For strings, localCompare is used. * The sort is stable if the browser uses a stable sort (all modern engines do) */ sort(...fields) { const arr = this._clone(); if (fields.length === 0) { fields = [identity]; } arr.sort((a, b) => { for (let i = 0, length = fields.length; i < length; i++) { const fieldA = fields[i](a); const fieldB = fields[i](b); // Non defined values always go at the end if (fieldA === null || fieldA === undefined) return 1; if (fieldB === null || fieldB === undefined) return -1; if (typeof fieldA === 'string') { const comparison = fieldA.localeCompare(fieldB); if (comparison !== 0) return comparison; } else if (fieldA < fieldB) return -1; else if (fieldA > fieldB) return 1; } return 0; }); return new ArrayWrapper(arr); } /** * Takes the first 'count' items from this Array. */ take(count) { return this.pipe(_ => _.slice(0, count)); } /** * Takes the last 'count' items from this Array. */ takeRight(count) { return this.pipe(arr => (arr.length < count ? arr.slice(0) : arr.slice(arr.length - count))); } /** * Converts this Array to a Set. */ toSet() { return this.pipe(arr => new Set(arr)); } /** * Updates an item at the specified index. */ updateAt(index, updater) { const result = this._clone(); if (result.length > index && index > -1) result[index] = getValue(updater(result[index])); return new ArrayWrapper(result); } } let pipe; export function setArrayPipe(_pipe) { pipe = _pipe; } /* * Returns a number[] wrapper with all numbers from start to stop (inclusive), * incremented or decremented by step. */ export function range(start, stop, step) { if (arguments.length === 1) { stop = arguments[0] - 1; start = 0; } step = step || 1; const result = []; const increasing = step > 0; let next = start; while ((increasing && next <= stop) || (!increasing && next >= stop)) { result.push(next); next = next + step; } return new ArrayWrapper(result); }