Files

435 lines
11 KiB
TypeScript

/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { expect } from 'chai';
import { SortedMap, LLRBNode } from '../src/core/util/SortedMap';
import { shuffle } from './helpers/util';
// Many of these were adapted from the mugs source code.
// http://mads379.github.com/mugs/
describe('SortedMap Tests', () => {
const defaultCmp = function (a, b) {
if (a === b) {
return 0;
} else if (a < b) {
return -1;
} else {
return 1;
}
};
it('Create node', () => {
const map = new SortedMap(defaultCmp).insert('key', 'value');
expect((map as any).root_.left.isEmpty()).to.equal(true);
expect((map as any).root_.right.isEmpty()).to.equal(true);
});
it('You can search a map for a specific key', () => {
const map = new SortedMap(defaultCmp).insert(1, 1).insert(2, 2);
expect(map.get(1)).to.equal(1);
expect(map.get(2)).to.equal(2);
expect(map.get(3)).to.equal(null);
});
it('You can insert a new key/value pair into the tree', () => {
const map = new SortedMap(defaultCmp).insert(1, 1).insert(2, 2);
expect((map as any).root_.key).to.equal(2);
expect((map as any).root_.left.key).to.equal(1);
});
it('You can remove a key/value pair from the map', () => {
const map = new SortedMap(defaultCmp).insert(1, 1).insert(2, 2);
const newMap = map.remove(1);
expect(newMap.get(2)).to.equal(2);
expect(newMap.get(1)).to.equal(null);
});
it('More removals', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(50, 50)
.insert(3, 3)
.insert(4, 4)
.insert(7, 7)
.insert(9, 9)
.insert(20, 20)
.insert(18, 18)
.insert(2, 2)
.insert(71, 71)
.insert(42, 42)
.insert(88, 88);
const m1 = map.remove(7);
const m2 = m1.remove(3);
const m3 = m2.remove(1);
expect(m3.count()).to.equal(9);
expect(m3.get(1)).to.equal(null);
expect(m3.get(3)).to.equal(null);
expect(m3.get(7)).to.equal(null);
expect(m3.get(20)).to.equal(20);
});
it('Removal bug', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(2, 2)
.insert(3, 3);
const m1 = map.remove(2);
expect(m1.get(1)).to.equal(1);
expect(m1.get(3)).to.equal(3);
});
it('Test increasing', () => {
const total = 100;
let item;
let map = new SortedMap(defaultCmp).insert(1, 1);
for (item = 2; item < total; item++) {
map = map.insert(item, item);
}
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
for (item = 2; item < total; item++) {
map = map.remove(item);
}
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
});
it('The structure should be valid after insertion (1)', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(2, 2)
.insert(3, 3);
expect((map as any).root_.key).to.equal(2);
expect((map as any).root_.left.key).to.equal(1);
expect((map as any).root_.right.key).to.equal(3);
});
it('The structure should be valid after insertion (2)', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(2, 2)
.insert(3, 3)
.insert(4, 4)
.insert(5, 5)
.insert(6, 6)
.insert(7, 7)
.insert(8, 8)
.insert(9, 9)
.insert(10, 10)
.insert(11, 11)
.insert(12, 12);
expect(map.count()).to.equal(12);
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
});
it('Rotate left leaves the tree in a valid state', () => {
const node = new LLRBNode(
4,
4,
false,
new LLRBNode(2, 2, false, null, null),
new LLRBNode(
7,
7,
true,
new LLRBNode(5, 5, false, null, null),
new LLRBNode(8, 8, false, null, null)
)
);
const node2 = (node as any).rotateLeft_();
expect(node2.count()).to.equal(5);
expect(node2.checkMaxDepth_()).to.equal(true);
});
it('Rotate right leaves the tree in a valid state', () => {
const node = new LLRBNode(
7,
7,
false,
new LLRBNode(
4,
4,
true,
new LLRBNode(2, 2, false, null, null),
new LLRBNode(5, 5, false, null, null)
),
new LLRBNode(8, 8, false, null, null)
);
const node2 = (node as any).rotateRight_();
expect(node2.count()).to.equal(5);
expect(node2.key).to.equal(4);
expect(node2.left.key).to.equal(2);
expect(node2.right.key).to.equal(7);
expect(node2.right.left.key).to.equal(5);
expect(node2.right.right.key).to.equal(8);
});
it('The structure should be valid after insertion (3)', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(50, 50)
.insert(3, 3)
.insert(4, 4)
.insert(7, 7)
.insert(9, 9);
expect(map.count()).to.equal(6);
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
const m2 = map.insert(20, 20).insert(18, 18).insert(2, 2);
expect(m2.count()).to.equal(9);
expect((m2 as any).root_.checkMaxDepth_()).to.equal(true);
const m3 = m2.insert(71, 71).insert(42, 42).insert(88, 88);
expect(m3.count()).to.equal(12);
expect((m3 as any).root_.checkMaxDepth_()).to.equal(true);
});
it('you can overwrite a value', () => {
const map = new SortedMap(defaultCmp).insert(10, 10).insert(10, 8);
expect(map.get(10)).to.equal(8);
});
it('removing the last element returns an empty map', () => {
const map = new SortedMap(defaultCmp).insert(10, 10).remove(10);
expect(map.isEmpty()).to.equal(true);
});
it('empty .get()', () => {
const empty = new SortedMap(defaultCmp);
expect(empty.get('something')).to.equal(null);
});
it('empty .count()', () => {
const empty = new SortedMap(defaultCmp);
expect(empty.count()).to.equal(0);
});
it('empty .remove()', () => {
const empty = new SortedMap(defaultCmp);
expect(empty.remove('something').count()).to.equal(0);
});
it('.reverseTraversal() works.', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(5, 5)
.insert(3, 3)
.insert(2, 2)
.insert(4, 4);
let next = 5;
map.reverseTraversal((key, value) => {
expect(key).to.equal(next);
next--;
});
expect(next).to.equal(0);
});
it('insertion and removal of 100 items in random order.', () => {
const N = 100;
const toInsert = [],
toRemove = [];
for (let i = 0; i < N; i++) {
toInsert.push(i);
toRemove.push(i);
}
shuffle(toInsert);
shuffle(toRemove);
let map = new SortedMap(defaultCmp);
for (let i = 0; i < N; i++) {
map = map.insert(toInsert[i], toInsert[i]);
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
}
expect(map.count()).to.equal(N);
// Ensure order is correct.
let next = 0;
map.inorderTraversal((key, value) => {
expect(key).to.equal(next);
expect(value).to.equal(next);
next++;
});
expect(next).to.equal(N);
for (let i = 0; i < N; i++) {
expect((map as any).root_.checkMaxDepth_()).to.equal(true);
map = map.remove(toRemove[i]);
}
expect(map.count()).to.equal(0);
});
// A little perf test for convenient benchmarking.
xit('Perf', () => {
for (let j = 0; j < 5; j++) {
let map = new SortedMap(defaultCmp);
const start = new Date().getTime();
for (let i = 0; i < 50000; i++) {
map = map.insert(i, i);
}
for (let i = 0; i < 50000; i++) {
map = map.remove(i);
}
const end = new Date().getTime();
// console.log(end-start);
}
});
xit('Perf: Insertion and removal with various # of items.', () => {
const verifyTraversal = function (map, max) {
let next = 0;
map.inorderTraversal((key, value) => {
expect(key).to.equal(next);
expect(value).to.equal(next);
next++;
});
expect(next).to.equal(max);
};
for (let N = 10; N <= 100000; N *= 10) {
const toInsert = [],
toRemove = [];
for (let i = 0; i < N; i++) {
toInsert.push(i);
toRemove.push(i);
}
shuffle(toInsert);
shuffle(toRemove);
let map = new SortedMap(defaultCmp);
const start = new Date().getTime();
for (let i = 0; i < N; i++) {
map = map.insert(toInsert[i], toInsert[i]);
}
// Ensure order is correct.
verifyTraversal(map, N);
for (let i = 0; i < N; i++) {
map = map.remove(toRemove[i]);
}
const elapsed = new Date().getTime() - start;
// console.log(N + ": " +elapsed);
}
});
xit('Perf: Comparison with {}: Insertion and removal with various # of items.', () => {
const verifyTraversal = function (tree, max) {
const keys = [];
for (const k of Object.keys(tree)) {
keys.push(k);
}
keys.sort();
expect(keys.length).to.equal(max);
for (let i = 0; i < max; i++) {
expect(tree[i]).to.equal(i);
}
};
for (let N = 10; N <= 100000; N *= 10) {
const toInsert = [],
toRemove = [];
for (let i = 0; i < N; i++) {
toInsert.push(i);
toRemove.push(i);
}
shuffle(toInsert);
shuffle(toRemove);
const tree = {};
const start = new Date().getTime();
for (let i = 0; i < N; i++) {
tree[i] = i;
}
// Ensure order is correct.
//verifyTraversal(tree, N);
for (let i = 0; i < N; i++) {
delete tree[i];
}
const elapsed = new Date().getTime() - start;
// console.log(N + ": " +elapsed);
}
});
it('SortedMapIterator empty test.', () => {
const map = new SortedMap(defaultCmp);
const iterator = map.getIterator();
expect(iterator.getNext()).to.equal(null);
});
it('SortedMapIterator test with 10 items.', () => {
const items = [];
for (let i = 0; i < 10; i++) {
items.push(i);
}
shuffle(items);
let map = new SortedMap(defaultCmp);
for (let i = 0; i < 10; i++) {
map = map.insert(items[i], items[i]);
}
const iterator = map.getIterator();
let n = iterator.getNext() as any,
expected = 0;
while (n !== null) {
expect(n.key).to.equal(expected);
expect(n.value).to.equal(expected);
expected++;
n = iterator.getNext();
}
expect(expected).to.equal(10);
});
it('SortedMap.getPredecessorKey works.', () => {
const map = new SortedMap(defaultCmp)
.insert(1, 1)
.insert(50, 50)
.insert(3, 3)
.insert(4, 4)
.insert(7, 7)
.insert(9, 9);
expect(map.getPredecessorKey(1)).to.equal(null);
expect(map.getPredecessorKey(3)).to.equal(1);
expect(map.getPredecessorKey(4)).to.equal(3);
expect(map.getPredecessorKey(7)).to.equal(4);
expect(map.getPredecessorKey(9)).to.equal(7);
expect(map.getPredecessorKey(50)).to.equal(9);
});
});