refactor(core): Add array modification functions. (#34804)

This change introduces several functions for manipulating items in an array in an efficient (binary search) way.

- `arraySplice` a faster version of `Array.splice()`.
- `arrayInsert` a faster version of `Array.splice(index, 0, value)`.
- `arrayInsert2` a faster version of `Array.splice(index, 0, value1, value2)`.
- `arrayInsertSorted` a way to insert a value into sorted list.
- `arrayRemoveSorted` a way to remove a value from a sorted list.
- `arrayIndexOfSorted` a way to find a value in a sorted list.
- `ArrayMap` Efficient implementation of `Map` as an `Array`.
- `arrayMapSet`, `arrayMapGet`, `arrayMapIndexOf`, and `arrayMapDelete` for manipulating `ArrayMap`s.

PR Close #34804
This commit is contained in:
Misko Hevery
2019-11-22 20:15:14 -08:00
committed by Miško Hevery
parent 49e8028f26
commit 3bbe1d9f30
2 changed files with 432 additions and 13 deletions

View File

@ -6,20 +6,173 @@
* found in the LICENSE file at https://angular.io/license
*/
import {flatten} from '../../src/util/array_utils';
import {ArrayMap, arrayIndexOfSorted, arrayInsert, arrayInsert2, arrayInsertSorted, arrayMapDelete, arrayMapGet, arrayMapIndexOf, arrayMapSet, arrayRemoveSorted, arraySplice, flatten} from '../../src/util/array_utils';
describe('flatten', () => {
describe('array_utils', () => {
it('should flatten an empty array', () => { expect(flatten([])).toEqual([]); });
describe('flatten', () => {
it('should flatten a flat array', () => { expect(flatten([1, 2, 3])).toEqual([1, 2, 3]); });
it('should flatten an empty array', () => { expect(flatten([])).toEqual([]); });
it('should flatten a nested array depth-first', () => {
expect(flatten([1, [2], 3])).toEqual([1, 2, 3]);
expect(flatten([[1], 2, [3]])).toEqual([1, 2, 3]);
expect(flatten([1, [2, [3]], 4])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [4]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [[[4]]]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [], 2])).toEqual([1, 2]);
it('should flatten a flat array', () => { expect(flatten([1, 2, 3])).toEqual([1, 2, 3]); });
it('should flatten a nested array depth-first', () => {
expect(flatten([1, [2], 3])).toEqual([1, 2, 3]);
expect(flatten([[1], 2, [3]])).toEqual([1, 2, 3]);
expect(flatten([1, [2, [3]], 4])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [4]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [2, [3]], [[[4]]]])).toEqual([1, 2, 3, 4]);
expect(flatten([1, [], 2])).toEqual([1, 2]);
});
});
});
describe('fast arraySplice', () => {
function expectArraySplice(array: any[], index: number) {
arraySplice(array, index, 1);
return expect(array);
}
it('should remove items', () => {
expectArraySplice([0, 1, 2], 0).toEqual([1, 2]);
expectArraySplice([0, 1, 2], 1).toEqual([0, 2]);
expectArraySplice([0, 1, 2], 2).toEqual([0, 1]);
});
});
describe('arrayInsertSorted', () => {
function expectArrayInsert(array: any[], index: number, value: any) {
arrayInsert(array, index, value);
return expect(array);
}
function expectArrayInsert2(array: any[], index: number, value1: any, value2: any) {
arrayInsert2(array, index, value1, value2);
return expect(array);
}
it('should insert items', () => {
expectArrayInsert([], 0, 'A').toEqual(['A']);
expectArrayInsert([0], 0, 'A').toEqual(['A', 0]);
expectArrayInsert([0], 1, 'A').toEqual([0, 'A']);
expectArrayInsert([0, 1, 2], 0, 'A').toEqual(['A', 0, 1, 2]);
expectArrayInsert([0, 1, 2], 1, 'A').toEqual([0, 'A', 1, 2]);
expectArrayInsert([0, 1, 2], 2, 'A').toEqual([0, 1, 'A', 2]);
expectArrayInsert([0, 1, 2], 3, 'A').toEqual([0, 1, 2, 'A']);
});
it('should insert items', () => {
expectArrayInsert2([], 0, 'A', 'B').toEqual(['A', 'B']);
expectArrayInsert2([0], 0, 'A', 'B').toEqual(['A', 'B', 0]);
expectArrayInsert2([0], 1, 'A', 'B').toEqual([0, 'A', 'B']);
expectArrayInsert2([0, 1, 2], 0, 'A', 'B').toEqual(['A', 'B', 0, 1, 2]);
expectArrayInsert2([0, 1, 2], 1, 'A', 'B').toEqual([0, 'A', 'B', 1, 2]);
expectArrayInsert2([0, 1, 2], 2, 'A', 'B').toEqual([0, 1, 'A', 'B', 2]);
expectArrayInsert2([0, 1, 2], 3, 'A', 'B').toEqual([0, 1, 2, 'A', 'B']);
expectArrayInsert2(['height', '1px', 'width', '2000px'], 0, 'color', 'red').toEqual([
'color', 'red', 'height', '1px', 'width', '2000px'
]);
});
});
describe('arrayInsertSorted', () => {
it('should insert items don\'t allow duplicates', () => {
let a;
a = ['a', 'c', 'e', 'g', 'i'];
expect(arrayInsertSorted(a, 'a')).toEqual(0);
expect(a).toEqual(['a', 'c', 'e', 'g', 'i']);
a = ['a', 'c', 'e', 'g', 'i'];
expect(arrayInsertSorted(a, 'b')).toEqual(1);
expect(a).toEqual(['a', 'b', 'c', 'e', 'g', 'i']);
a = ['a', 'c', 'e', 'g', 'i'];
expect(arrayInsertSorted(a, 'c')).toEqual(1);
expect(a).toEqual(['a', 'c', 'e', 'g', 'i']);
a = ['a', 'c', 'e', 'g', 'i'];
expect(arrayInsertSorted(a, 'd')).toEqual(2);
expect(a).toEqual(['a', 'c', 'd', 'e', 'g', 'i']);
a = ['a', 'c', 'e', 'g', 'i'];
expect(arrayInsertSorted(a, 'e')).toEqual(2);
expect(a).toEqual(['a', 'c', 'e', 'g', 'i']);
});
});
describe('arrayRemoveSorted', () => {
it('should remove items', () => {
let a;
a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayRemoveSorted(a, 'a')).toEqual(0);
expect(a).toEqual(['b', 'c', 'd', 'e']);
a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayRemoveSorted(a, 'b')).toEqual(1);
expect(a).toEqual(['a', 'c', 'd', 'e']);
a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayRemoveSorted(a, 'c')).toEqual(2);
expect(a).toEqual(['a', 'b', 'd', 'e']);
a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayRemoveSorted(a, 'd')).toEqual(3);
expect(a).toEqual(['a', 'b', 'c', 'e']);
a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayRemoveSorted(a, 'e')).toEqual(4);
expect(a).toEqual(['a', 'b', 'c', 'd']);
});
});
describe('arrayIndexOfSorted', () => {
it('should get index of', () => {
const a = ['a', 'b', 'c', 'd', 'e'];
expect(arrayIndexOfSorted(a, 'a')).toEqual(0);
expect(arrayIndexOfSorted(a, 'b')).toEqual(1);
expect(arrayIndexOfSorted(a, 'c')).toEqual(2);
expect(arrayIndexOfSorted(a, 'd')).toEqual(3);
expect(arrayIndexOfSorted(a, 'e')).toEqual(4);
});
});
describe('ArrayMap', () => {
it('should support basic operations', () => {
const map: ArrayMap<number> = [] as any;
expect(arrayMapIndexOf(map, 'A')).toEqual(~0);
expect(arrayMapSet(map, 'B', 1)).toEqual(0);
expect(map).toEqual(['B', 1]);
expect(arrayMapIndexOf(map, 'B')).toEqual(0);
expect(arrayMapSet(map, 'A', 0)).toEqual(0);
expect(map).toEqual(['A', 0, 'B', 1]);
expect(arrayMapIndexOf(map, 'B')).toEqual(2);
expect(arrayMapIndexOf(map, 'AA')).toEqual(~2);
expect(arrayMapSet(map, 'C', 2)).toEqual(4);
expect(map).toEqual(['A', 0, 'B', 1, 'C', 2]);
expect(arrayMapGet(map, 'A')).toEqual(0);
expect(arrayMapGet(map, 'B')).toEqual(1);
expect(arrayMapGet(map, 'C')).toEqual(2);
expect(arrayMapGet(map, 'AA')).toEqual(undefined);
expect(arrayMapSet(map, 'B', -1)).toEqual(2);
expect(map).toEqual(['A', 0, 'B', -1, 'C', 2]);
expect(arrayMapDelete(map, 'AA')).toEqual(~2);
expect(arrayMapDelete(map, 'B')).toEqual(2);
expect(map).toEqual(['A', 0, 'C', 2]);
expect(arrayMapDelete(map, 'A')).toEqual(0);
expect(map).toEqual(['C', 2]);
expect(arrayMapDelete(map, 'C')).toEqual(0);
expect(map).toEqual([]);
});
});
});