From 06b2f7e3a901fa1ff69d1e687f7aaaf32fced427 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Sat, 21 Oct 2017 15:32:10 +0200 Subject: [PATCH] collections --- package-lock.json | Bin 165468 -> 154251 bytes package.json | 10 ++-- src/structure/collections/hash-set.ts | 50 +++++++++++++++++++ src/structure/collections/linked-index.ts | 58 ++++++++++++++++++++++ src/structure/collections/linked-set.ts | 1 - src/structure/collections/range-set.ts | 41 +++++++++++++++ src/structure/spec/collections.spec.ts | 6 +++ 7 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 src/structure/collections/hash-set.ts create mode 100644 src/structure/collections/linked-index.ts delete mode 100644 src/structure/collections/linked-set.ts diff --git a/package-lock.json b/package-lock.json index fbf52d4b90d0f4e49c73635a1e0e111e5f3bff45..47e6bce95bb6fce12ea990a40b6cdc0d9bde06e8 100644 GIT binary patch delta 1724 zcmZ9MYfO`86vuNOgqAx@E~0X+D6}Z;+Y8iH=!IKp>4gHNxMibM+d|8wv_QLP6hACF z*_0=G5}ld4IkQNdj?8YF8TV<%OlP)a%O)RoNfwiBio58%EqmG`OZMf>$(!?>^FP1y zf1ZW?X|H{ldM*U?L^^ly29KVFT%4W-L61%!G;P;K6eHsr^OVbD?{IcIoVJMA+tF$W zO*OTJYQ^6EvElJngVm+1H#@lY`etpg$2u_*8IZbrCoCS$Aq{B;yIS32j*0p}^PplZ zqM_Ec0%`&tb&@hr(DD>Xtm!3^OAo$RN-eo7x&oa;7TJ(f-tN$E*Nk);`kR~sE=!+w zpvz=-M{JtWaKvLVDYq#Co*u8QU1PVl`fR=WUb%aqPVVg#bA#R?&uAnp4ULDSlF5!X zpKm+;^xIWbU6?iRO==gyAfEpL)=;%Vg~tk^k{*!e#yaDzLg=T(r-am8mx;TyWF6kt zl6?B}nS452lR|x>^!dvh=Ssjya~F&7x4V#y$A5(?tXhQfSP?wl2Px=UgqQHxk03(x zW8lrE@~DcJh6VSa1nciX6%`3m(SMIozWNAO;;Yk;iIaPw46ns9zWxXD(V%CoO^=~0 zy5SDI3-dLCi-3{6kdD56aDDcA#sxoF2bl9a`(w<8YE&PBEZp@sT%~Vyq|6T+7xLz_ zoe%2K^8^a8VGqf`r++{eoikOTgAg&bw5-IPe?ldG{tl!wE}8Qyj<&yoIR%g&y<7@A zk`E`5d@Qp-GPYDe4h`+eqod+F6xXspZ<esVJwx_ONdg&8mqH;fe93ggj~OZ<mp=U| z6U+MGd7P_+s@ZUUG}{CpCt-mQR???EnQQ@yM9HuaYL-i07D7MnC?c!SZ)22ht_OZ} z!3wGsxIzqh)a=UmPm4zko0bp0FJ=d4s)-Q4$s%Pax(MayZiEVK+sZz-BuwCa2^7<< zGfN^bH8Ox(IN(#Cc}eLZ4r-WE{ElOD?cXPgs_n3fk5^s<9{N?VitgBxXn#`$`sL2t zmL&>|U^2aOQkZ~{+Q9V3@vAMM!oh|(1w7NhbTWeoW584Jjuuwq7iJdu8lf1^#iGwD zm>1%a(F_8-Tfs(&r(~*OZ6exaW5<jafsYO~<e*;-YyLOhQ30gUGs--ScNc0v7@N$m zVOn1(j+s0K)3i`TM`b+R6bp84<YAcEp4JHXn5|+P?5(a!aJ;Q$4vi~Vmd57H$6D4S zA@s-wri@80r~32~d{Re5_)rH$SkM$ReiN+6(>kKU`e|}^iB2!`-e0Un+#-*g65!R% z&)}%7Y`{0n3~24i(I!-g5WaRf3C^GFu<JY-mC4&m@lGRee`9>NCvDu)QVJ0#;a zgIlJMPs$`c?Gr8H!ANL8EFPceZ<S~bHg|U<Fe)}%ZI+SRdJpFl`MC*;+&yM%u>|D9 zoz9k)R*$`JYFKYK*7Z$Uea1SCUGJv3rxdXZ$&Vg-1lN);B#{8bwOrtl(}3oOY-Q$j zl7oxwB#SQ2$zrND$FEsq3aLa@3R|`%jU`;a&n&_GSbzsI2oH~pL00rQpUfo6MK5KL z6EYeC4qw-hLY!XDCh~N3>L;emgW;jJZJ~aXPT4!;G7X7b&OlINazuKhIz_F{B<l(* z{kovm9%wR}blt(>u+nW9(YM>g-NDJ=wvf%=>>4q;1Dw-q6giu4cQsKgTL8b;68`Ck zmL%f`X&}S1t>ihVr@N&UtJ}o>pgk;?PH~e8rAca#_ZUK5gASWwisnwqV-;qr%2CND zf@RlWppD!B36>T>ZFEFW`VvnS3694gRS*e>ng>vaoPp%AwDTNP7E3lNO}+tHWN5(K p+cxC%bu~vO>zq1oo68=y(i4?xEYg!~e7gk-VsBG)!9aFx{uf$1HB$fp delta 3599 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 diff --git a/package.json b/package.json index a024c3177..6e12b2ee1 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 000000000..8cf25df55 --- /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 000000000..0f40f2d0d --- /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 9749ede22..000000000 --- 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 cd243cc37..6543f9428 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 7506936f3..848ec4803 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); -- GitLab