import { assert } from 'chai'; import { HashMap } from '../../../lib/containers/HashMap'; type Vec = number[]; function sumVec(vec: Vec) { let ret = 0; const length = vec.length; for (let i = 0; i < length; i++) ret += vec[i]; return ret; } function eqVec(lhs: Vec, rhs: Vec) { const length = lhs.length; if (length !== rhs.length) return false; for (let i = 0; i < length; i++) if (lhs[i] !== rhs[i]) return false; return true; } function compVec(lhs: Vec, rhs: Vec) { const lhsLength = lhs.length; const rhsLength = rhs.length; const minLength = Math.min(lhsLength, rhsLength); for (let i = 0; i < minLength; i++) { const result = lhs[i] - rhs[i]; if (result !== 0) return result; } return lhsLength - rhsLength; } function sortVec(vecArr: [Vec, any][]) { return vecArr.slice().sort((a, b) => compVec(a[0], b[0])); } describe('containers.HashMap', () => { describe('constructor', () => { it('require hash and equal functions', () => { const map = new HashMap(sumVec, eqVec); assert.strictEqual(map['_hash'], sumVec); assert.strictEqual(map['_equal'], eqVec); assert.strictEqual(map.size, 0); }); it('should accept optional entries argument', () => { const map = new HashMap(sumVec, eqVec, [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); assert.strictEqual(map['_hash'], sumVec); assert.strictEqual(map['_equal'], eqVec); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); }); it('should accept optional iterable entries argument', () => { const map = new HashMap(sumVec, eqVec, (function* () { yield [[1, 2], 'a'] as [Vec, string]; yield [[2, 3], 'b'] as [Vec, string]; yield [[3, 4], 'c'] as [Vec, string]; }())); assert.strictEqual(map['_hash'], sumVec); assert.strictEqual(map['_equal'], eqVec); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); }); it('should accept optional array-like entries argument', () => { const map = new HashMap(sumVec, eqVec, { 0: [[1, 2], 'a'], 1: [[2, 3], 'b'], 2: [[3, 4], 'c'], length: 3 }); assert.strictEqual(map['_hash'], sumVec); assert.strictEqual(map['_equal'], eqVec); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); }); }); describe('size', () => { it('should provide size property', () => { const map = new HashMap(sumVec, eqVec); assert.strictEqual(map.size, 0); map.set([1, 2], 'a'); assert.strictEqual(map.size, 1); map.set([2, 3], 'b'); assert.strictEqual(map.size, 2); }); }); describe('set', () => { it('should set different values for different keys', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); }); it('should set same values for different keys', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'x'); map.set([2, 3], 'x'); map.set([3, 4], 'x'); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'x'], [[2, 3], 'x'], [[3, 4], 'x']]);; }); it('should update values for same keys', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); map.set([2, 3], 'x'); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'x'], [[3, 4], 'c']]); }); it('should set values with keys hash collisions', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'ab'); map.set([0, 3], 'za'); map.set([3, 4], 'cd'); map.set([-1, 4], 'yd'); map.set([2, 2, 2, 1], 'bbba'); assert.deepEqual(sortVec([...map.entries()]), [[[-1, 4], 'yd'], [[0, 3], 'za'], [[1, 2], 'ab'], [[2, 2, 2, 1], 'bbba'], [[3, 4], 'cd']]); }); }); describe('get', () => { it('should get values by keys', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'ab'); map.set([0, 3], 'zc'); map.set([3, 4], 'cd'); map.set([-1, 4], 'yd'); map.set([2, 2, 2, 1], 'bbba'); assert.strictEqual(map.get([3, 4]), 'cd'); assert.strictEqual(map.get([-1, 4]), 'yd'); assert.isUndefined(map.get([4, 5])); assert.isUndefined(map.get([1, 1, 1])); assert.strictEqual(map.get([2, 2, 2, 1]), 'bbba'); }); }); describe('has', () => { it('should test keys by value', () => { const map = new HashMap(sumVec, eqVec); assert.isFalse(map.has([1, 2])); map.set([1, 2], 3); assert.isTrue(map.has([1, 2])); assert.isFalse(map.has([2, 3])); assert.isFalse(map.has([0, 3])); assert.isFalse(map.has([2, 1])); map.set([2, 1], 3); assert.isTrue(map.has([2, 1])); assert.isFalse(map.has([0, 3])); map.set([2, 3], 5); assert.isTrue(map.has([2, 3])); }); }); describe('delete', () => { it('should delete items by keys', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.isTrue(map.delete([2, 3])); assert.strictEqual(map.size, 2); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[3, 4], 'c']]); assert.isFalse(map.delete([4, 5])); assert.isFalse(map.delete([0, 3])); assert.strictEqual(map.size, 2); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[3, 4], 'c']]); map.set([2, 3], 'b'); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); assert.isTrue(map.delete([2, 3])); assert.strictEqual(map.size, 2); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[3, 4], 'c']]); }); it('should delete items with keys hash collisions', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'ab'); map.set([2, 3], 'bc'); map.set([3, 4], 'cd'); map.set([0, 3], 'zc'); map.set([1, 1, 1], 'aaa'); assert.isTrue(map.delete([0, 3])); assert.strictEqual(map.size, 4); assert.deepEqual(sortVec([...map.entries()]), [[[1, 1, 1], 'aaa'], [[1, 2], 'ab'], [[2, 3], 'bc'], [[3, 4], 'cd']]); assert.isFalse(map.delete([0, 3])); assert.strictEqual(map.size, 4); assert.deepEqual(sortVec([...map.entries()]), [[[1, 1, 1], 'aaa'], [[1, 2], 'ab'], [[2, 3], 'bc'], [[3, 4], 'cd']]); assert.isTrue(map.delete([1, 1, 1])); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'ab'], [[2, 3], 'bc'], [[3, 4], 'cd']]); }); it('should delete items with hash collisions', () => { const map = new HashMap(() => 0, (lhs, rhs) => lhs === rhs); map.set('foo', true); map.set('bar', true); map.set('buzz', true); assert.deepEqual([...map.keys()].sort(), ['foo', 'bar', 'buzz'].sort()); map.delete('foo'); assert.deepEqual([...map.keys()].sort(), ['bar', 'buzz'].sort()); map.delete('buzz'); assert.deepEqual([...map.keys()], ['bar']); map.delete('bar'); assert.strictEqual(map.size, 0); }); }); describe('clear', () => { it('should clear empty set', () => { const map = new HashMap(sumVec, eqVec); map.clear(); assert.strictEqual(map.size, 0); assert.deepEqual([...map.entries()], []); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); }); it('should clear non empty set', () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.strictEqual(map.size, 3); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]); map.clear(); assert.strictEqual(map.size, 0); assert.deepEqual([...map.entries()], []); map.set([1, 2], 'a'); map.set([4, 5], 'e'); assert.strictEqual(map.size, 2); assert.deepEqual(sortVec([...map.entries()]), [[[1, 2], 'a'], [[4, 5], 'e']]); }); }); describe('keys', () => { it(`should support 'keys' iteration`, () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.deepEqual([...map.keys()].sort(compVec), [[1, 2], [2, 3], [3, 4]]); }); }); describe('values', () => { it(`should support 'values' iteration`, () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); map.set([3, 4], 'c'); assert.deepEqual([...map.values()].sort(), ['a', 'b', 'c']); }); }); describe('entries', () => { it(`should support 'entries' iteration`, () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); assert.deepEqual([...map.entries()], [[[1, 2], 'a'], [[2, 3], 'b']]); }); }); describe('Symbol.iterator', () => { it(`should support 'Symbol.iterator' iteration`, () => { const map = new HashMap(sumVec, eqVec); map.set([1, 2], 'a'); map.set([2, 3], 'b'); assert.deepEqual([...map], [[[1, 2], 'a'], [[2, 3], 'b']]); }); }); describe('forEach', () => { it(`should support 'forEach' iteration`, () => { const scope = {}; const entries: [Vec, string][] = [[[1, 2], 'a'], [[2, 3], 'b'], [[3, 4], 'c']]; const map = new HashMap(sumVec, eqVec, entries); const result: [Vec, string][] = []; map.forEach(function (this: any, value, key, subject) { assert.strictEqual(map, subject); assert.strictEqual(this, scope); result.push([key, value]); }, scope); assert.deepEqual(result, entries); }); }); });