/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow * @format */ export function arrayRemove(array: Array, element: T): void { const index = array.indexOf(element); if (index >= 0) { array.splice(index, 1); } } export function arrayEqual( array1: Array, array2: Array, equalComparator?: (a: T, b: T) => boolean, ): boolean { if (array1.length !== array2.length) { return false; } const equalFunction = equalComparator || ((a: T, b: T) => a === b); return array1.every((item1, i) => equalFunction(item1, array2[i])); } /** * Returns a copy of the input Array with all `null` and `undefined` values filtered out. * Allows Flow to typecheck the common `filter(x => x != null)` pattern. */ export function arrayCompact(array: Array): Array { const result = []; for (const elem of array) { if (elem != null) { result.push(elem); } } return result; } /** * Flattens an Array> into just an Array */ export function arrayFlatten(array: Array>): Array { const result = []; for (const subArray of array) { result.push(...subArray); } return result; } /** * Removes duplicates from Array. * Uses SameValueZero for equality purposes, which is like '===' except it deems * two NaNs equal. http://www.ecma-international.org/ecma-262/6.0/#sec-samevaluezero */ export function arrayUnique(array: Array): Array { return Array.from(new Set(array)); } /** * Returns the last index in the input array that matches the predicate. * Returns -1 if no match is found. */ export function arrayFindLastIndex( array: Array, predicate: (elem: T, index: number, array: Array) => boolean, thisArg?: any, ): number { for (let i = array.length - 1; i >= 0; i--) { if (predicate.call(thisArg, array[i], i, array)) { return i; } } return -1; } /** * Merges a given arguments of maps into one Map, with the latest maps * overriding the values of the prior maps. */ export function mapUnion(...maps: Array>): Map { const unionMap = new Map(); for (const map of maps) { for (const [key, value] of map) { unionMap.set(key, value); } } return unionMap; } export function mapCompact(map: Map): Map { const selected = new Map(); for (const [key, value] of map) { if (value != null) { selected.set(key, value); } } return selected; } export function mapFilter( map: Map, selector: (key: T, value: X) => boolean, ): Map { const selected = new Map(); for (const [key, value] of map) { if (selector(key, value)) { selected.set(key, value); } } return selected; } export function mapTransform( src: Map, transform: (value: V1, key: T) => V2, ): Map { const result = new Map(); for (const [key, value] of src) { result.set(key, transform(value, key)); } return result; } export function mapEqual( map1: Map, map2: Map, equalComparator?: (val1: X, val2: X, key1?: T, key2?: T) => boolean, ) { if (map1.size !== map2.size) { return false; } const equalFunction = equalComparator || ((a: X, b: X) => a === b); for (const [key1, value1] of map1) { if (!map2.has(key1) || !equalFunction(value1, (map2.get(key1): any))) { return false; } } return true; } export function mapGetWithDefault( map: Map, key: K, default_: V, ): V { if (map.has(key)) { // Cast through `any` since map.get's return is a maybe type. We can't just get the value and // check it against `null`, since null/undefined may inhabit V. We know this is safe since we // just checked that the map has the key. return (map.get(key): any); } else { return default_; } } export function areSetsEqual(a: Set, b: Set): boolean { return a.size === b.size && every(a, element => b.has(element)); } // Array.every but for any iterable. export function every( values: Iterable, predicate: (element: T) => boolean, ): boolean { for (const element of values) { if (!predicate(element)) { return false; } } return true; } export function setIntersect(a: Set, b: Set): Set { return setFilter(a, e => b.has(e)); } export function setUnion(a: Set, b: Set): Set { // Avoids the extra Array allocations that `new Set([...a, ...b])` would incur. Some quick tests // indicate it would be about 60% slower. const result = new Set(a); b.forEach(x => { result.add(x); }); return result; } export function setDifference( a: Set, b: Set, hash_?: (v: T) => any, ): Set { if (a.size === 0) { return new Set(); } else if (b.size === 0) { return new Set(a); } const result = new Set(); const hash = hash_ || (x => x); const bHashes = hash_ == null ? b : new Set(Array.from(b.values()).map(hash)); a.forEach(value => { if (!bHashes.has(hash(value))) { result.add(value); } }); return result; } export function setFilter( set: Set, predicate: (value: T) => boolean, ): Set { const out = new Set(); for (const item of set) { if (predicate(item)) { out.add(item); } } return out; } /** * O(1)-check if a given object is empty (has no properties, inherited or not) */ export function isEmpty(obj: Object): boolean { for (const key in obj) { return false; } return true; } /** * Constructs an enumeration with keys equal to their value. * e.g. keyMirror({a: null, b: null}) => {a: 'a', b: 'b'} * * Based off the equivalent function in www. */ export function keyMirror(obj: T): {[key: $Enum]: $Enum} { const ret = {}; Object.keys(obj).forEach(key => { ret[key] = key; }); return ret; } /** * Given an array of [key, value] pairs, construct a map where the values for * each key are collected into an array of values, in order. */ export function collect(pairs: Array<[K, V]>): Map> { const result = new Map(); for (const pair of pairs) { const [k, v] = pair; let list = result.get(k); if (list == null) { list = []; result.set(k, list); } list.push(v); } return result; } export class MultiMap { // Invariant: no empty sets. They should be removed instead. _map: Map>; // TODO may be worth defining a getter but no setter, to mimic Map. But please just behave and // don't mutate this from outside this class. // // Invariant: equal to the sum of the sizes of all the sets contained in this._map /* The total number of key-value bindings contained */ size: number; constructor() { this._map = new Map(); this.size = 0; } /* * Returns the set of values associated with the given key. Do not mutate the given set. Copy it * if you need to store it past the next operation on this MultiMap. */ get(key: K): Set { const set = this._map.get(key); if (set == null) { return new Set(); } return set; } /* * Mimics the Map.prototype.set interface. Deliberately did not choose "set" as the name since the * implication is that it removes the previous binding. */ add(key: K, value: V): MultiMap { let set = this._map.get(key); if (set == null) { set = new Set(); this._map.set(key, set); } if (!set.has(value)) { set.add(value); this.size++; } return this; } /* * Mimics the Map.prototype.set interface. Replaces the previous binding with new values. */ set(key: K, values: Iterable): void { this.deleteAll(key); const newSet = new Set(values); if (newSet.size !== 0) { this._map.set(key, newSet); this.size += newSet.size; } } /* * Deletes a single binding. Returns true iff the binding existed. */ delete(key: K, value: V): boolean { const set = this.get(key); const didRemove = set.delete(value); if (set.size === 0) { this._map.delete(key); } if (didRemove) { this.size--; } return didRemove; } /* * Deletes all bindings associated with the given key. Returns true iff any bindings were deleted. */ deleteAll(key: K): boolean { const set = this.get(key); this.size -= set.size; return this._map.delete(key); } clear(): void { this._map.clear(); this.size = 0; } has(key: K, value: V): boolean { return this.get(key).has(value); } hasAny(key: K): boolean { return this._map.has(key); } *values(): Iterable { for (const set of this._map.values()) { yield* set; } } forEach(callback: (value: V, key: K, obj: MultiMap) => void): void { this._map.forEach((values, key) => values.forEach(value => callback(value, key, this)), ); } } export function objectEntries(obj: {[key: string]: T}): Array<[string, T]> { if (obj == null) { throw new TypeError(); } const entries = []; for (const key in obj) { if ( obj.hasOwnProperty(key) && Object.prototype.propertyIsEnumerable.call(obj, key) ) { entries.push([key, obj[key]]); } } return entries; } export function objectFromMap(map: Map): {[key: string]: T} { const obj = {}; map.forEach((v, k) => { obj[k] = v; }); return obj; } export function* concatIterators( ...iterators: Array> ): Iterator { for (const iterator of iterators) { for (const element of iterator) { yield element; } } } export function someOfIterable( iterable: Iterable, predicate: (element: T) => boolean, ): boolean { for (const element of iterable) { if (predicate(element)) { return true; } } return false; } export function findInIterable( iterable: Iterable, predicate: (element: T) => boolean, ): ?T { for (const element of iterable) { if (predicate(element)) { return element; } } return null; } export function* filterIterable( iterable: Iterable, predicate: (element: T) => boolean, ): Iterable { for (const element of iterable) { if (predicate(element)) { yield element; } } } export function* mapIterable( iterable: Iterable, projectorFn: (element: T) => M, ): Iterable { for (const element of iterable) { yield projectorFn(element); } } export function firstOfIterable(iterable: Iterable): ?T { return findInIterable(iterable, () => true); } export function iterableIsEmpty(iterable: Iterable): boolean { // eslint-disable-next-line no-unused-vars for (const element of iterable) { return false; } return true; } export function iterableContains(iterable: Iterable, value: T): boolean { return !iterableIsEmpty( filterIterable(iterable, element => element === value), ); }