diff --git a/src/modules/esl-utils/README.md b/src/modules/esl-utils/README.md index 1ac813e10..58f0e5d99 100644 --- a/src/modules/esl-utils/README.md +++ b/src/modules/esl-utils/README.md @@ -85,6 +85,8 @@ Even if you use the whole utils module, it is still tiny. - ##### [Array](./misc/array.ts) - array utils (uniq, flat, wrap, etc.). + - ##### [Set](./misc/set.ts) - set/array with uniq values utils + - ##### [Format](./misc/format.ts) - string format utils. - ##### [Function](./misc/functions.ts) - simple functions and types. diff --git a/src/modules/esl-utils/all.ts b/src/modules/esl-utils/all.ts index 0ab64e644..4842d5b3c 100644 --- a/src/modules/esl-utils/all.ts +++ b/src/modules/esl-utils/all.ts @@ -1,10 +1,12 @@ -// Common +// Miscellanies export * as UID from './misc/uid'; +export * as SetUtils from './misc/set'; export * as ArrayUtils from './misc/array'; export * as ObjectUtils from './misc/object'; export * as FormatUtils from './misc/format'; export * as FunctionUtils from './misc/functions'; +// Memoization decorator export * from './misc/memoize'; // Common diff --git a/src/modules/esl-utils/misc/array.ts b/src/modules/esl-utils/misc/array.ts index 44b70923f..a727e0dea 100644 --- a/src/modules/esl-utils/misc/array.ts +++ b/src/modules/esl-utils/misc/array.ts @@ -9,11 +9,11 @@ export const tuple = (arr: T[]): Tuple[] => arr.reduce((acc: Tuple[], e return acc; }, []); -/** Flat array */ +/** Flat array - unwraps one level of nested arrays */ export const flat = (arr: (null | T | T[])[]): T[] => arr.reduce((acc: T[], el) => el ? acc.concat(el) : acc, []) as T[]; -/** Wrap to array */ +/** Wrap passed object to array */ export const wrap = (arr: undefined | null | T | T[]): T[] => { if (arr === undefined || arr === null) return []; if (Array.isArray(arr)) return arr; @@ -29,9 +29,9 @@ export const uniq = (arr: T[]): T[] => { return result; }; -/** Crete an array filled with the range [0,..,N-1] */ +/** Create an array filled with the range [0,..,N-1] */ export function range(n: number): number[]; -/** Crete an array filled with values returned by the filler callback */ +/** Create an array filled with values returned by the filler callback */ export function range(n: number, filler: (i: number) => T): T[]; export function range(n: number, filler: (i: number) => any = identity): any[] { const arr = Array(n); diff --git a/src/modules/esl-utils/misc/set.ts b/src/modules/esl-utils/misc/set.ts new file mode 100644 index 000000000..82eac466a --- /dev/null +++ b/src/modules/esl-utils/misc/set.ts @@ -0,0 +1,36 @@ +/** Create an array of unique values that presented in each of the passed arrays */ +export function intersection(...rest: T[][]): T[]; +export function intersection(a: T[], b: T[], ...rest: T[][]): T[] { + if (arguments.length < 2) return a ? a.slice() : []; + if (rest.length) return intersection(a, intersection(b, ...rest)); + return a.filter(Set.prototype.has, new Set(b)); +} + +/** Create an array with unique values from each of the passed arrays */ +export function union(...rest: T[][]): T[] { + const set = new Set(); + rest.forEach(item => item.forEach(i => set.add(i))); + const result: any[] = []; + set.forEach(value => result.push(value)); + return result; +} + +/** Creates an array of unique values from the first array that are not present in the other arrays */ +export function complement(...rest: T[][]): T[]; +export function complement(a: T[], b: T[], ...rest: T[][]): T[] | undefined { + if (!b) return a ? a.slice() : []; + if (rest.length > 1) return complement(a, complement(b, ...rest)); + const setB = new Set(b); + return a.filter(element => !setB.has(element)); +} + +/** + * @returns if the passed arrays have a full intersection + * Expect uniq values in collections + */ +export function fullIntersection(a: T[], b: T[]): boolean { + if (a.length === 0 && b.length === 0) return true; + const [larger, smaller] = a.length >= b.length ? [a, b] : [b, a]; + const set = new Set(larger); + return !smaller.filter((item: T) => !set.has(item)).length; +} diff --git a/src/modules/esl-utils/misc/test/set.test.ts b/src/modules/esl-utils/misc/test/set.test.ts new file mode 100644 index 000000000..3045fbb6c --- /dev/null +++ b/src/modules/esl-utils/misc/test/set.test.ts @@ -0,0 +1,50 @@ +import {complement, fullIntersection, intersection, union} from '../set'; + +describe('set', () => { + test('intersection', () => { + const a = {a: 3}; + expect(intersection()).toEqual([]); + expect(intersection([1])).toEqual([1]); + expect(intersection([],[])).toEqual([]); + expect(intersection([1],[1])).toEqual([1]); + expect(intersection([1, 2, 3], [1, 2], [1, 2, 4])).toEqual([1, 2]); + expect(intersection([1, [2, 3], a], [a, 1, [2, 3]])).toEqual([1, a]); + expect(intersection([null, 1, 2, 3, [6]], [4, 5])).toEqual([]); + expect(intersection([1, 2, 3, [6]], [4, 5, 1, 2], [1])).toEqual([1]); + expect(intersection([NaN],[NaN])).toEqual([NaN]); + }); + + test('union', () => { + const a = {a:3}; + expect(union()).toEqual([]); + expect(union([1])).toEqual([1]); + expect(union([], [])).toEqual([]); + expect(union([1], [1])).toEqual([1]); + expect(union([1, 2], [1, 2], [1, 2])).toEqual([1, 2,]); + expect(union([1, [2, 3], a], [a, 1, [2, 3]])).toEqual([1, [2, 3], a, [2, 3]]); + expect(union([1, 2, 3, null, [6]], [4, 5], [1, 2, 3, null, [6]], [4, 5])).toEqual([1, 2, 3, null, [6], 4, 5, [6]]); + expect(union([NaN],[NaN])).toEqual([NaN]); + }); + + test('complement', () => { + const a = {a: 3}; + expect(complement()).toEqual([]); + expect(complement([1])).toEqual([1]); + expect(complement([],[])).toEqual([]); + expect(complement([1],[1])).toEqual([]); + expect(complement([1, 2, 3], [1, 2], [1, 2, 5])).toEqual([3]); + expect(complement([1, [2, 3], a], [a, 1, [2, 3]])).toEqual([[2, 3]]); + expect(complement([null, 1, 2, 3, [6]], [4, 5])).toEqual([null, 1, 2, 3,[6]]); + expect(complement([NaN],[NaN])).toEqual([]); + }); + + test('fullIntersection', () => { + const a = {a: 3}; + expect(fullIntersection([],[])).toEqual(true); + expect(fullIntersection([1],[1])).toEqual(true); + expect(fullIntersection([1, 2, 3], [1, 2])).toEqual(true); + expect(fullIntersection([1, [2, 3], a], [a, 1, [2, 3]])).toEqual(false); + expect(fullIntersection([null, 1, 2, 3, [6]], [4, 5])).toEqual(false); + expect(fullIntersection([null, 1, 2, 3, [6]], [4, 5, null, 1, 2, 3, [6]])).toEqual(false); + }); +})