diff --git a/package-lock.json b/package-lock.json index fbf52d4b90d0f4e49c73635a1e0e111e5f3bff45..47e6bce95bb6fce12ea990a40b6cdc0d9bde06e8 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index a024c317721b89e841d890b053bfa501c0fb2fa5..6e12b2ee17dfd788b1e1a22b3e5612844619a464 100644 --- a/package.json +++ b/package.json @@ -30,19 +30,19 @@ "license": "MIT", "devDependencies": { "@types/benchmark": "^1.0.30", - "@types/jest": "^21.1.3", - "@types/node": "^8.0.41", + "@types/jest": "^21.1.4", + "@types/node": "^8.0.46", "benchmark": "^2.1.4", "download-cli": "^1.0.5", "jest": "^21.2.1", "rollup": "^0.50.0", "rollup-plugin-buble": "^0.16.0", - "rollup-plugin-commonjs": "^8.2.1", + "rollup-plugin-commonjs": "^8.2.4", "rollup-plugin-json": "^2.3.0", "rollup-plugin-node-resolve": "^3.0.0", "rollup-watch": "^4.3.1", - "ts-jest": "^21.1.2", - "tslint": "^5.7.0", + "ts-jest": "^21.1.3", + "tslint": "^5.8.0", "typescript": "^2.5.3", "uglify-js": "^3.1.4", "util.promisify": "^1.0.0" diff --git a/src/structure/collections/hash-set.ts b/src/structure/collections/hash-set.ts new file mode 100644 index 0000000000000000000000000000000000000000..8cf25df5585d98566e939beeedf6345de4e86d55 --- /dev/null +++ b/src/structure/collections/hash-set.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +interface SetLike<T> { + readonly size: number; + add(a: T): boolean; + has(a: T): boolean; +} + +class HashSetImpl<T> implements SetLike<T> { + size: number = 0; + private byHash: { [hash: number]: T[] } = Object.create(null); + + add(a: T) { + const hash = this.getHash(a); + if (this.byHash[hash]) { + const xs = this.byHash[hash]; + for (const x of xs) { + if (this.areEqual(a, x)) return false; + } + xs[xs.length] = a; + this.size++; + return true; + } else { + this.byHash[hash] = [a]; + this.size++; + return true; + } + } + + has(v: T) { + const hash = this.getHash(v); + if (!this.byHash[hash]) return false; + for (const x of this.byHash[hash]) { + if (this.areEqual(v, x)) return true; + } + return false; + } + + constructor(private getHash: (v: T) => any, private areEqual: (a: T, b: T) => boolean) { } +} + +function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> { + return new HashSetImpl<T>(getHash, areEqual); +} + +export default HashSet; \ No newline at end of file diff --git a/src/structure/collections/linked-index.ts b/src/structure/collections/linked-index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f40f2d0ddafa491cf1a6ef1833c4e20a86379a3 --- /dev/null +++ b/src/structure/collections/linked-index.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +/** A data structure useful for graph traversal */ +interface LinkedIndex { + readonly head: number, + has(i: number): boolean, + remove(i: number): void +} + +function LinkedIndex(size: number): LinkedIndex { + return new LinkedIndexImpl(size); +} + +class LinkedIndexImpl implements LinkedIndex { + private prev: Int32Array; + private next: Int32Array; + head: number; + + remove(i: number) { + const { prev, next } = this; + const p = prev[i], n = next[i]; + if (p >= 0) { + next[p] = n; + prev[i] = -1; + } + if (n >= 0) { + prev[n] = p; + next[i] = -1; + } + if (i === this.head) { + if (p < 0) this.head = n; + else this.head = p; + } + } + + has(i: number) { + return this.prev[i] >= 0 || this.next[i] >= 0; + } + + constructor(size: number) { + this.head = size > 0 ? 0 : -1; + this.prev = new Int32Array(size); + this.next = new Int32Array(size); + + for (let i = 0; i < size; i++) { + this.next[i] = i + 1; + this.prev[i] = i - 1; + } + this.prev[0] = -1; + this.next[size - 1] = -1; + } +} + +export default LinkedIndex; \ No newline at end of file diff --git a/src/structure/collections/linked-set.ts b/src/structure/collections/linked-set.ts deleted file mode 100644 index 9749ede223e770b6e476b697f8339c6e5ece5409..0000000000000000000000000000000000000000 --- a/src/structure/collections/linked-set.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO: fixed length doubly linked list used for graph traversal \ No newline at end of file diff --git a/src/structure/collections/range-set.ts b/src/structure/collections/range-set.ts index cd243cc378b6a39bd4e418f6470afb607ae6d5b7..6543f94287b773d374c525ec7bbcde3a4fdb86b5 100644 --- a/src/structure/collections/range-set.ts +++ b/src/structure/collections/range-set.ts @@ -21,6 +21,29 @@ namespace RangeSet { toArray(): ArrayLike<number> } + export function hashCode(a: RangeSet) { + // hash of tuple (size, min, max, mid) + const { size } = a; + let hash = 23; + if (!size) return hash; + hash = 31 * hash + size; + hash = 31 * hash + a.elementAt(0); + hash = 31 * hash + a.elementAt(size - 1); + if (size > 2) hash = 31 * hash + a.elementAt(size >> 1); + return hash; + } + + export function areEqual(a: RangeSet, b: RangeSet) { + if (a === b) return true; + if (a instanceof RangeImpl) { + if (b instanceof RangeImpl) return a.min === b.min && a.max === b.max; + return equalAR(b as ArrayImpl, a); + } else if (b instanceof RangeImpl) { + return equalAR(a as ArrayImpl, b); + } + return equalAA(a as ArrayImpl, b as ArrayImpl); + } + export function union(a: RangeSet, b: RangeSet) { if (a instanceof RangeImpl) { if (b instanceof RangeImpl) return unionRR(a, b); @@ -107,6 +130,20 @@ namespace RangeSet { return -1; } + function equalAR(a: ArrayImpl, b: RangeImpl) { + return a.size === b.size && a.min === b.min && a.max === b.max; + } + + function equalAA(a: ArrayImpl, b: ArrayImpl) { + if (a.size !== b.size || a.min !== b.min || a.max !== b.max) return false; + const { size, values: xs } = a; + const { values: ys } = b; + for (let i = 0; i < size; i++) { + if (xs[i] !== ys[i]) return false; + } + return true; + } + function areRangesIntersecting(a: Impl, b: Impl) { return a.size > 0 && b.size > 0 && a.max >= b.min && a.min <= b.max; } @@ -150,6 +187,8 @@ namespace RangeSet { function unionAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { const la = xs.length, lb = ys.length; + // sorted list merge. + let i = 0, j = 0, resultSize = 0; while (i < la && j < lb) { const x = xs[i], y = ys[j]; @@ -203,6 +242,8 @@ namespace RangeSet { function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { const la = xs.length, lb = ys.length; + // a variation on sorted list merge. + let i = 0, j = 0, resultSize = 0; while (i < la && j < lb) { const x = xs[i], y = ys[j]; diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index 7506936f393d798f3f691265126004e69b78abb5..848ec48037aa608e95d1f276fcc220b6885427d2 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -137,6 +137,12 @@ describe('range set', () => { testEq('range', range, [1, 2, 3, 4]); testEq('sorted array', arr, [1, 3, 6]); + expect(RangeSet.areEqual(empty, singleton)).toBe(false); + expect(RangeSet.areEqual(singleton, singleton)).toBe(true); + expect(RangeSet.areEqual(range, singleton)).toBe(false); + expect(RangeSet.areEqual(arr, RangeSet.ofSortedArray([1, 3, 6]))).toBe(true); + expect(RangeSet.areEqual(arr, RangeSet.ofSortedArray([1, 4, 6]))).toBe(false); + expect(empty.has(10)).toBe(false); expect(empty.indexOf(10)).toBe(-1);