// missing from JS stdlib // return an array of numbers starting at `start`, not reaching `end`, incrementing by `step`. export function range(start: number, end: number, step: number = 1): number[] { return [...Array(Math.ceil((end - start) / step)).keys()].map(i => i * step + start); } // return the average of an array of numbers. export function average(array: number[]): number { return array.reduce((sum, n) => sum + n) / array.length; } // ----- arrays // this is really just a trick to make typescript happy. export function removeMissing(list: (A | undefined)[]): A[] { return list.filter(x => x !== undefined) as A[]; } // remove an item discovered via `indexOf`. export function arrayRemove(array: Array, item: A) { const index = array.indexOf(item); if (index >= 0) array.splice(index, 1); } // call `f` on each item of the array, and return a new array containing only the results that were not undefined. export function filterMap(list: A[], f: (item: A, index: number) => B | undefined): B[] { return list.reduce((rv, item, i) => { const b = f(item, i); if (b !== undefined) rv.push(b); return rv; }, []); } // return the index of the first element where test returns true. if none did, return the array length. export function binarySearch(array: A[], test: (item: A) => boolean): number { let lo = -1, hi = array.length; while (lo + 1 < hi) { const m = lo + ((hi - lo) >> 1); if (test(array[m])) { hi = m; } else { lo = m; } } return hi; } // break an array into a list of arrays where each new array's size is `maxSize` except (possibly) the last one. export function arraySlice(array: A[], maxSize: number): A[][] { return range(0, array.length, maxSize).map(i => array.slice(i, i + maxSize)); } export function groupBy(array: A[], grouper: (a: A) => string): { [key: string]: A[] } { const rv: { [key: string]: A[] } = {}; array.forEach(a => { const key = grouper(a); if (rv[key] === undefined) rv[key] = []; rv[key].push(a); }); return rv; } // return a list that only contains each item once, based on a lookup per item. export function uniqueBy(list: A[], getKey: (item: A) => string): A[] { const seenKeys = new Set(); return list.filter(x => { const key = getKey(x); if (seenKeys.has(key)) return false; seenKeys.add(key); return true; }); } // return two lists: the first is every item where `f` returned true, the second is every item where `f` returned false. export function partition(array: A[], f: (item: A) => boolean): [ A[], A[] ] { const left: A[] = []; const right: A[] = []; array.forEach(item => (f(item) ? left : right).push(item)); return [ left, right ]; } // return the most popular item from a list. export function consensus( array: Array, comparer: (a: A, b: A) => number ): A | undefined { const sorted: Array<[ A, number ]> = array.sort(comparer).map(item => [ item, 1 ] as [ A, number ]); if (sorted.length == 0) return undefined; let i = 1; while (i < sorted.length) { if (comparer(sorted[i - 1][0], sorted[i][0]) == 0) { sorted[i - 1][1]++; sorted.splice(i, 1); } else { i++; } } return sorted.sort((a, b) => b[1] - a[1])[0][0]; } // ----- iterators // generate a list of numbers starting at `start`. each subsequent item is // supplied by `next`, until `condition` returns false. export function generate( start: number, next: (n: number) => number, condition: (n: number, len: number) => boolean ): number[] { const rv: number[] = []; let current = start; do { rv.push(current); current = next(current); } while (condition(current, rv.length)); return rv; } // make an iterator that yields the first `max` items. export function* iterTake(iterable: Iterable, max: number): Iterable { const iter = iterable[Symbol.iterator](); for (let i = 0; i < max; i++) { const item = iter.next(); if (item.done) return; yield item.value; } } // return an iterable that concatenates all the iterables in `array`. export function *iterFlatten(array: Iterable[]): Iterable { for (const x of array) yield* x; } // ----- maps // like `map`, but applying only to the keys of a Map. export function mapKeys(inMap: Map, f: (key: A) => B): Map { return new Map([...inMap].map(([ k, v ]) => [ f(k), v ])); } // like `filterMap`, but applying only to the keys of a Map. export function filterMapKeys(inMap: Map, f: (key: A) => (B | undefined)): Map { return new Map(removeMissing([...inMap].map(([ k, v ]) => { const newKey = f(k); return newKey === undefined ? undefined : [ newKey, v ]; }))); } // ----- promises // like `filter`, for a list of promises. export async function asyncFilter(list: A[], f: (item: A) => Promise): Promise { const allowed = await Promise.all(list.map(f)); return list.filter((x, i) => allowed[i]); } // return true if any of the promises returned true. export async function asyncSome(list: A[], f: (item: A) => Promise): Promise { return (await Promise.all(list.map(f))).some(x => x); }