Don't sort the list on every render (#58)

This commit is contained in:
Maciej Hirsz
2018-09-27 17:01:51 +02:00
committed by GitHub
parent 87facf1552
commit 62d2847bb0
13 changed files with 416 additions and 67 deletions
+5 -1
View File
@@ -10,6 +10,10 @@
"node": ">=9.5"
},
"scripts": {
"test": "echo \"Tests only available from root wrapper\""
"test": "node ./test | tap-spec"
},
"devDependencies": {
"tap-spec": "^5.0.0",
"tape": "^4.9.1"
}
}
+78
View File
@@ -112,3 +112,81 @@ export class NumStats<T extends number> {
return this.index < this.history ? this.stack.slice(0, this.index) : this.stack;
}
}
/**
* Insert an item into a sorted array using binary search.
*
* @type {T} item type
* @param {T} item to be inserted
* @param {Array<T>} array to be modified
* @param {(a, b) => number} compare function
*
* @return {number} insertion index
*/
export function sortedInsert<T>(item: T, into: Array<T>, compare: (a: T, b: T) => number): number {
if (into.length === 0) {
into.push(item);
return 0;
}
let min = 0;
let max = into.length - 1;
while (min !== max) {
const guess = (min + max) / 2 | 0;
if (compare(item, into[guess]) < 0) {
max = Math.max(min, guess - 1);
} else {
min = Math.min(max, guess + 1);
}
}
let insert = compare(item, into[min]) <= 0 ? min : min + 1;
into.splice(insert, 0, item);
return insert;
}
/**
* Find an index of an element within a sorted array. This should be substantially
* faster than `indexOf` for large arrays.
*
* @type {T} item type
* @param {T} item to find
* @param {Array<T>} array to look through
* @param {(a, b) => number} compare function
*
* @return {number} index of the element, `-1` if not found
*/
export function sortedIndexOf<T>(item:T, within: Array<T>, compare: (a: T, b: T) => number): number {
if (within.length === 0) {
return -1;
}
let min = 0;
let max = within.length - 1;
while (min !== max) {
const guess = (min + max) / 2 | 0;
const other = within[guess];
if (item === other) {
return guess;
}
if (compare(item, other) < 0) {
max = Math.max(min, guess - 1);
} else {
min = Math.min(max, guess + 1);
}
}
if (item === within[min]) {
return min;
}
return -1;
}
+94
View File
@@ -0,0 +1,94 @@
const test = require('tape');
const common = require('../build/');
test('sortedInsert', (assert) => {
const { sortedInsert } = common;
const cmp = (a, b) => a - b;
let mod = sortedInsert(3, [1,2,4,5], cmp);
function assertInsert(item, into, equals) {
sortedInsert(item, into, cmp);
assert.same(into, equals, `Inserts ${item}`);
}
assertInsert(1, [2,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(2, [1,3,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(3, [1,2,4,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(4, [1,2,3,5,6,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(5, [1,2,3,4,6,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(6, [1,2,3,4,5,7,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(7, [1,2,3,4,5,6,8,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(8, [1,2,3,4,5,6,7,9], [1,2,3,4,5,6,7,8,9]);
assertInsert(9, [1,2,3,4,5,6,7,8], [1,2,3,4,5,6,7,8,9]);
assert.end();
});
test('sortedInsert fuzz', (assert) => {
const { sortedInsert } = common;
const cmp = (a, b) => a - b;
const scramble = () => Math.random() - 0.5;
const sorted = [1,2,3,4,5,6,7,8,9];
for (let i = 0; i < 50; i++) {
const scrambled = sorted.sort(scramble);
const resorted = [];
for (const item of scrambled) {
sortedInsert(item, resorted, cmp);
}
assert.same(resorted, [1,2,3,4,5,6,7,8,9], `resort ${scrambled}`);
}
assert.end();
});
test('sortedInsert indexes', (assert) => {
const { sortedInsert } = common;
const cmp = (a, b) => a - b;
const into = [];
assert.equals(sortedInsert(5, into, cmp), 0, 'Insert 5');
assert.same(into, [5], 'Elements check out');
assert.equals(sortedInsert(1, into, cmp), 0, 'Insert 1');
assert.same(into, [1,5], 'Elements check out');
assert.equals(sortedInsert(9, into, cmp), 2, 'Insert 9');
assert.same(into, [1,5,9], 'Elements check out');
assert.equals(sortedInsert(3, into, cmp), 1, 'Insert 3');
assert.same(into, [1,3,5,9], 'Elements check out');
assert.equals(sortedInsert(7, into, cmp), 3, 'Insert 7');
assert.same(into, [1,3,5,7,9], 'Elements check out');
assert.equals(sortedInsert(4, into, cmp), 2, 'Insert 4');
assert.same(into, [1,3,4,5,7,9], 'Elements check out');
assert.equals(sortedInsert(6, into, cmp), 4, 'Insert 6');
assert.same(into, [1,3,4,5,6,7,9], 'Elements check out');
assert.equals(sortedInsert(2, into, cmp), 1, 'Insert 2');
assert.same(into, [1,2,3,4,5,6,7,9], 'Elements check out');
assert.equals(sortedInsert(8, into, cmp), 7, 'Insert 8');
assert.same(into, [1,2,3,4,5,6,7,8,9], 'Elements check out');
assert.end();
});
test('sortedIndexOf', (assert) => {
const { sortedIndexOf } = common;
const cmp = (a, b) => a - b;
assert.equals(sortedIndexOf(1, [1,2,3,4,5,6,7,8,9], cmp), 0, 'Found 1');
assert.equals(sortedIndexOf(2, [1,2,3,4,5,6,7,8,9], cmp), 1, 'Found 2');
assert.equals(sortedIndexOf(3, [1,2,3,4,5,6,7,8,9], cmp), 2, 'Found 3');
assert.equals(sortedIndexOf(4, [1,2,3,4,5,6,7,8,9], cmp), 3, 'Found 4');
assert.equals(sortedIndexOf(5, [1,2,3,4,5,6,7,8,9], cmp), 4, 'Found 5');
assert.equals(sortedIndexOf(6, [1,2,3,4,5,6,7,8,9], cmp), 5, 'Found 6');
assert.equals(sortedIndexOf(7, [1,2,3,4,5,6,7,8,9], cmp), 6, 'Found 7');
assert.equals(sortedIndexOf(8, [1,2,3,4,5,6,7,8,9], cmp), 7, 'Found 8');
assert.equals(sortedIndexOf(9, [1,2,3,4,5,6,7,8,9], cmp), 8, 'Found 9');
assert.equals(sortedIndexOf(0, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 0');
assert.equals(sortedIndexOf(10, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 10');
assert.equals(sortedIndexOf(5.5, [1,2,3,4,5,6,7,8,9], cmp), -1, 'No 5.5');
assert.end();
});