diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 3580392466e6df2b49fff85df07e42b08d85ff1b..f7f35b74cf6386e3954687b575795946d5b28939 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -1,5 +1,5 @@ import * as B from 'benchmark' -import IntPair from '../structure/collections/int-pair' +import IntTuple from '../structure/collections/int-tuple' import OrdSet from '../structure/collections/ordered-set' import MSet from '../structure/collections/multi-set' @@ -33,7 +33,7 @@ namespace Iteration { export function elementAt() { let s = 0; - for (let i = 0, _i = MSet.size(ms); i < _i; i++) s += IntPair.snd(MSet.get(ms, i)); + for (let i = 0, _i = MSet.size(ms); i < _i; i++) s += IntTuple.snd(MSet.getAt(ms, i)); return s; } @@ -41,9 +41,9 @@ namespace Iteration { let s = 0; const keys = MSet.keys(ms); for (let i = 0, _i = OrdSet.size(keys); i < _i; i++) { - const set = MSet.getSetByKey(ms, OrdSet.get(keys, i)); + const set = MSet.getByKey(ms, OrdSet.getAt(keys, i)); for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - s += OrdSet.get(set, j); + s += OrdSet.getAt(set, j); } } return s; @@ -51,10 +51,10 @@ namespace Iteration { export function manual1() { let s = 0; - for (let i = 0, _i = MSet.getSetCount(ms); i < _i; i++) { - const set = MSet.getSetByIndex(ms, i); + for (let i = 0, _i = MSet.keyCount(ms); i < _i; i++) { + const set = MSet.getByIndex(ms, i); for (let j = 0, _j = OrdSet.size(set); j < _j; j++) { - s += OrdSet.get(set, j); + s += OrdSet.getAt(set, j); } } return s; @@ -75,4 +75,4 @@ suite .add('manual1', () => Iteration.manual1()) .add('el at', () => Iteration.elementAt()) .on('cycle', (e: any) => console.log(String(e.target))) - .run(); + .run(); \ No newline at end of file diff --git a/src/structure/collections/hash-functions.ts b/src/structure/collections/hash-functions.ts index 542559ae50afd24fbf558d1c3118b34313855b3f..1394e40a453e42e9ca54f431169985a79f15a8db 100644 --- a/src/structure/collections/hash-functions.ts +++ b/src/structure/collections/hash-functions.ts @@ -43,4 +43,12 @@ export function hash4(i: number, j: number, k: number, l: number) { a = (a ^ 0xdeadbeef) + (a << 5); a = a ^ (a >> 11); return a; +} + +/** + * A unique number for each pair of integers + * Biggest representable pair is (67108863, 67108863) (limit imposed by Number.MAX_SAFE_INTEGER) + */ +export function cantorPairing(a: number, b: number) { + return (a + b) * (a + b + 1) / 2 + b; } \ No newline at end of file diff --git a/src/structure/collections/int-pair.ts b/src/structure/collections/int-tuple.ts similarity index 52% rename from src/structure/collections/int-pair.ts rename to src/structure/collections/int-tuple.ts index 910e796fd6c147e18c6509ac96eb8ef6b6a61927..4b13b209636edc2ad318382357ccd7d3576e856b 100644 --- a/src/structure/collections/int-pair.ts +++ b/src/structure/collections/int-tuple.ts @@ -6,10 +6,14 @@ import { hash2 } from './hash-functions' -interface IntPair { fst: number, snd: number } +/** + * Represents a pair of two integers as a double, + * Caution: === does not work, because of NaN, use IntTuple.areEqual for equality + */ +interface IntTuple extends Number { } -namespace IntPair { - export interface Packed extends Number { } +namespace IntTuple { + export interface Unpacked { fst: number, snd: number } const { _int32, _float64, _int32_1, _float64_1 } = (function() { const data = new ArrayBuffer(8); @@ -22,72 +26,73 @@ namespace IntPair { }; }()); - export function is(x: any): x is IntPair { + export function is(x: any): x is Unpacked { return !!x && typeof x.fst === 'number' && typeof x.snd === 'number'; } export function create(fst: number, snd: number) { return { fst, snd }; } - export function zero(): IntPair { return { fst: 0, snd: 0 }; } + export function zero(): Unpacked { return { fst: 0, snd: 0 }; } - export function pack1(fst: number, snd: number): number { + export function pack(fst: number, snd: number): IntTuple { _int32[0] = fst; _int32[1] = snd; return _float64[0]; } - export function pack(p: IntPair): number { - _int32[0] = p.fst; - _int32[1] = p.snd; + export function pack1(t: Unpacked): IntTuple { + _int32[0] = t.fst; + _int32[1] = t.snd; return _float64[0]; } - export function unpack(packed: Packed, target: IntPair): IntPair { - _float64[0] = packed as number; + export function unpack(t: IntTuple, target: Unpacked): Unpacked { + _float64[0] = t as number; target.fst = _int32[0]; target.snd = _int32[1]; return target; } - export function unpack1(packed: Packed): IntPair { + export function unpack1(packed: IntTuple): Unpacked { return unpack(packed, zero()); } - export function fst(packed: Packed): number { - _float64[0] = packed as number; + export function fst(t: IntTuple): number { + _float64[0] = t as number; return _int32[0]; } - export function snd(packed: Packed): number { - _float64[0] = packed as number; + export function snd(t: IntTuple): number { + _float64[0] = t as number; return _int32[1]; } - export function areEqual(a: Packed, b: Packed) { + /** Normal equality does not work, because NaN === NaN ~> false */ + export function areEqual(a: IntTuple, b: IntTuple) { _float64[0] = a as number; _float64_1[0] = b as number; return _int32[0] === _int32_1[0] && _int32[1] === _int32_1[1]; } - export function compare(a: number, b: number) { - _float64[0] = a; - _float64_1[0] = b; + export function compare(a: IntTuple, b: IntTuple) { + _float64[0] = a as number; + _float64_1[0] = b as number; const x = _int32[0] - _int32_1[0]; if (x !== 0) return x; return _int32[1] - _int32_1[1]; } - export function compareInArray(xs: ArrayLike<number>, i: number, j: number) { - _float64[0] = xs[i]; - _float64_1[0] = xs[j]; + export function compareInArray(xs: ArrayLike<IntTuple>, i: number, j: number) { + _float64[0] = xs[i] as number; + _float64_1[0] = xs[j] as number; const x = _int32[0] - _int32_1[0]; if (x !== 0) return x; return _int32[1] - _int32_1[1]; } - export function packedHashCode(packed: Packed) { - _float64[0] = packed as number; + export function hashCode(t: IntTuple) { + _float64[0] = t as number; return hash2(_int32[0], _int32[1]); } } -export default IntPair \ No newline at end of file +export default IntTuple \ No newline at end of file diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts index f7d908d1fcdd1a91cfd88506d3fca302dbb8b581..c3a9d2ef4fe5b859cfaf1735834bde3c6875dcbe 100644 --- a/src/structure/collections/multi-set.ts +++ b/src/structure/collections/multi-set.ts @@ -6,126 +6,121 @@ import OrderedSet from './ordered-set' import Iterator from './iterator' -import IntPair from './int-pair' +import IntTuple from './int-tuple' import { sortArray } from './sort' import { hash1 } from './hash-functions' -type MultiSetElements = { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet } -type MultiSet = number | MultiSetElements +/** A map-like representation of integer set */ +interface MultiSet { /*'@type': 'int-multi-set'*/ } namespace MultiSet { - export const Empty: MultiSet = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty }; + export const Empty: MultiSet = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty } as MultiSetElements as any; - export function create(data: IntPair.Packed | ArrayLike<IntPair.Packed> | IntPair | { [id: number]: OrderedSet }): MultiSet { + export function create(data: IntTuple | ArrayLike<IntTuple> | IntTuple | { [id: number]: OrderedSet }): MultiSet { if (typeof data === 'number') return data; - if (IntPair.is(data)) return IntPair.pack(data); - if (isArrayLike(data)) return ofPackedPairs(data); + if (IntTuple.is(data)) return IntTuple.pack1(data) as number; + if (isArrayLike(data)) return ofTuples(data); return ofObject(data as { [id: number]: OrderedSet }); } export function keys(set: MultiSet): OrderedSet { if (typeof set === 'number') return OrderedSet.ofSingleton(set); - return set.keys; + return (set as MultiSetElements).keys; } - export function getSetCount(set: MultiSet): number { + export function keyCount(set: MultiSet): number { if (typeof set === 'number') return 1; - return OrderedSet.size(set.keys); + return OrderedSet.size((set as MultiSetElements).keys); } export function hasKey(set: MultiSet, key: number): boolean { - if (typeof set === 'number') return IntPair.fst(set) === key; - return OrderedSet.has(set.keys, key); + if (typeof set === 'number') return IntTuple.fst(set) === key; + return OrderedSet.has((set as MultiSetElements).keys, key); } - const _hasP = IntPair.zero(); - export function has(set: MultiSet, pair: number): boolean { - if (typeof set === 'number') { - return IntPair.areEqual(pair, set); - } - IntPair.unpack(pair, _hasP); - return OrderedSet.has(set.keys, _hasP.fst) ? OrderedSet.has(set[_hasP.fst], _hasP.snd) : false; + export function getKey(set: MultiSet, index: number): number { + if (typeof set === 'number') return IntTuple.fst(set); + return OrderedSet.getAt((set as MultiSetElements).keys, index); + } + + export function has(set: MultiSet, t: IntTuple): boolean { + if (typeof set === 'number') return IntTuple.areEqual(t, set); + IntTuple.unpack(t, _hasP); + return OrderedSet.has((set as MultiSetElements).keys, _hasP.fst) ? OrderedSet.has((set as MultiSetElements)[_hasP.fst], _hasP.snd) : false; } + const _hasP = IntTuple.zero(); - const _gS = IntPair.zero(); - export function getSetByKey(set: MultiSet, key: number): OrderedSet { + export function getByKey(set: MultiSet, key: number): OrderedSet { if (typeof set === 'number') { - IntPair.unpack(set, _gS); + IntTuple.unpack(set, _gS); return _gS.fst === key ? OrderedSet.ofSingleton(_gS.snd) : OrderedSet.Empty; } - return OrderedSet.has(set.keys, key) ? set[key] : OrderedSet.Empty; + return OrderedSet.has((set as MultiSetElements).keys, key) ? (set as MultiSetElements)[key] : OrderedSet.Empty; } + const _gS = IntTuple.zero(); - export function getKey(set: MultiSet, index: number): number { - if (typeof set === 'number') return IntPair.fst(set); - return OrderedSet.get(set.keys, index); + export function getByIndex(set: MultiSet, index: number): OrderedSet { + if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(IntTuple.snd(set)) : OrderedSet.Empty; + const key = OrderedSet.getAt((set as MultiSetElements).keys, index); + return (set as MultiSetElements)[key] || OrderedSet.Empty; } - export function getSetByIndex(set: MultiSet, index: number): OrderedSet { - if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(IntPair.snd(set)) : OrderedSet.Empty; - const key = OrderedSet.get(set.keys, index); - return set[key] || OrderedSet.Empty; + export function getAt(set: MultiSet, i: number): IntTuple { + if (typeof set === 'number') return set; + return getAtE(set as MultiSetElements, i); } - export function get(set: MultiSet, i: number): IntPair.Packed { - if (typeof set === 'number') return set; - const { offsets, keys } = set; - const o = getOffsetIndex(offsets, i); - if (o >= offsets.length - 1) return 0; - const k = OrderedSet.get(keys, o); - const e = OrderedSet.get(set[k], i - offsets[o]); - return IntPair.pack1(k, e); + export function indexOf(set: MultiSet, t: IntTuple) { + if (typeof set === 'number') return IntTuple.areEqual(set, t) ? 0 : -1; + return indexOfE(set as MultiSetElements, t); } + /** Number elements in the "child" sets */ export function size(set: MultiSet) { - if (typeof set === 'number') return 0; - return set.offsets[set.offsets.length - 1]; + if (typeof set === 'number') return 1; + return (set as MultiSetElements).offsets[(set as MultiSetElements).offsets.length - 1]; } export function hashCode(set: MultiSet) { - if (typeof set === 'number') return IntPair.packedHashCode(set); - if (set.hashCode !== -1) return set.hashCode; - return computeHash(set); + if (typeof set === 'number') return IntTuple.hashCode(set); + if ((set as MultiSetElements).hashCode !== -1) return (set as MultiSetElements).hashCode; + return computeHash((set as MultiSetElements)); } export function areEqual(a: MultiSet, b: MultiSet): boolean { - if (a === b) return true; if (typeof a === 'number') { - if (typeof b === 'number') return a === b; + if (typeof b === 'number') return IntTuple.areEqual(a, b); return false; } if (typeof b === 'number') return false; - return areEqualEE(a, b); + return areEqualEE(a as MultiSetElements, b as MultiSetElements); } export function areIntersecting(a: MultiSet, b: MultiSet): boolean { - if (a === b) return true; if (typeof a === 'number') { - if (typeof b === 'number') return a === b; - return areIntersectingNE(a, b); + if (typeof b === 'number') return IntTuple.areEqual(a, b); + return areIntersectingNE(a, b as MultiSetElements); } - if (typeof b === 'number') return areIntersectingNE(b, a); - return areIntersectingEE(a, b); + if (typeof b === 'number') return areIntersectingNE(b, a as MultiSetElements); + return areIntersectingEE(a as MultiSetElements, b as MultiSetElements); } export function intersect(a: MultiSet, b: MultiSet): MultiSet { - if (a === b) return a; if (typeof a === 'number') { - if (typeof b === 'number') return a === b ? a : Empty; - return intersectNE(a, b); + if (typeof b === 'number') return IntTuple.areEqual(a, b) ? a : Empty; + return intersectNE(a, b as MultiSetElements); } - if (typeof b === 'number') return intersectNE(b, a); - return intersectEE(a, b); + if (typeof b === 'number') return intersectNE(b, a as MultiSetElements); + return intersectEE(a as MultiSetElements, b as MultiSetElements); } export function subtract(a: MultiSet, b: MultiSet): MultiSet { - if (a === b) return Empty; if (typeof a === 'number') { - if (typeof b === 'number') return a === b ? Empty : a; - return subtractNE(a, b); + if (typeof b === 'number') return IntTuple.areEqual(a, b) ? Empty : a; + return subtractNE(a, b as MultiSetElements); } - if (typeof b === 'number') return subtractEN(a, b); - return subtractEE(a, b); + if (typeof b === 'number') return subtractEN(a as MultiSetElements, b); + return subtractEE(a as MultiSetElements, b as MultiSetElements); } export function union(a: MultiSet, b: MultiSet): MultiSet { @@ -136,8 +131,8 @@ namespace MultiSet { return findUnion(sets); } - class ElementsIterator implements Iterator<IntPair> { - private pair = IntPair.zero(); + class ElementsIterator implements Iterator<IntTuple.Unpacked> { + private pair = IntTuple.zero(); private keyCount: number; private setIndex = -1; @@ -156,7 +151,7 @@ namespace MultiSet { if (!this.advance()) return this.pair; } - this.pair.snd = OrderedSet.get(this.currentSet, this.currentIndex++); + this.pair.snd = OrderedSet.getAt(this.currentSet, this.currentIndex++); return this.pair; } @@ -165,7 +160,7 @@ namespace MultiSet { this.done = true; return false; } - const unit = OrderedSet.get(this.elements.keys, this.setIndex); + const unit = OrderedSet.getAt(this.elements.keys, this.setIndex); this.pair.fst = unit; this.currentSet = this.elements[unit]; this.currentIndex = 0; @@ -180,12 +175,14 @@ namespace MultiSet { } } - export function values(set: MultiSet): Iterator<IntPair> { - if (typeof set === 'number') return Iterator.Value(IntPair.unpack1(set)); - return new ElementsIterator(set); + export function values(set: MultiSet): Iterator<IntTuple.Unpacked> { + if (typeof set === 'number') return Iterator.Value(IntTuple.unpack1(set)); + return new ElementsIterator(set as MultiSetElements); } } +interface MultiSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet } + function isArrayLike(x: any): x is ArrayLike<number> { return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer)); } @@ -199,7 +196,7 @@ function ofObject(data: { [id: number]: OrderedSet }) { if (!keys.length) return MultiSet.Empty; if (keys.length === 1) { const set = data[keys[0]]; - if (OrderedSet.size(set) === 1) return IntPair.pack1(keys[0], OrderedSet.get(set, 0)); + if (OrderedSet.size(set) === 1) return IntTuple.pack(keys[0], OrderedSet.getAt(set, 0)); } return ofObject1(keys, data); } @@ -208,7 +205,7 @@ function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { if (keys.length === 1) { const k = keys[0]; const set = data[k]; - if (OrderedSet.size(set) === 1) return IntPair.pack1(k, OrderedSet.get(set, 0)); + if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0)); } sortArray(keys); return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data); @@ -216,9 +213,9 @@ function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { if (OrderedSet.size(keys) === 1) { - const k = OrderedSet.get(keys, 0); + const k = OrderedSet.getAt(keys, 0); const set = data[k]; - if (OrderedSet.size(set) === 1) return IntPair.pack1(k, OrderedSet.get(set, 0)); + if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0)); } return _createObjectOrdered(keys, data); } @@ -229,7 +226,7 @@ function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet const offsets = [0]; let size = 0; for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.get(keys, i); + const k = OrderedSet.getAt(keys, i); const set = data[k]; ret[k] = set; size += OrderedSet.size(set); @@ -262,12 +259,12 @@ function normalizeArray(xs: number[]) { return xs; } -function ofPackedPairs(xs: ArrayLike<number>): MultiSet { +function ofTuples(xs: ArrayLike<IntTuple>): MultiSet { if (xs.length === 0) return MultiSet.Empty; const sets: { [key: number]: number[] } = Object.create(null); - const p = IntPair.zero(); + const p = IntTuple.zero(); for (let i = 0, _i = xs.length; i < _i; i++) { - IntPair.unpack(xs[i], p); + IntTuple.unpack(xs[i], p); const set = sets[p.fst]; if (set) set[set.length] = p.snd; else sets[p.fst] = [p.snd]; @@ -297,11 +294,31 @@ function getOffsetIndex(xs: ArrayLike<number>, value: number) { return value < xs[min] ? min - 1 : min; } +function getAtE(set: MultiSetElements, i: number) { + const { offsets, keys } = set; + const o = getOffsetIndex(offsets, i); + if (o >= offsets.length - 1) return 0; + const k = OrderedSet.getAt(keys, o); + const e = OrderedSet.getAt(set[k], i - offsets[o]); + return IntTuple.pack(k, e); +} + +const _iOE = IntTuple.zero(); +function indexOfE(set: MultiSetElements, t: IntTuple) { + IntTuple.unpack(t, _iOE); + const { keys } = set; + const setIdx = OrderedSet.indexOf(keys, _iOE.fst); + if (setIdx < 0) return -1; + const o = OrderedSet.indexOf(set[_iOE.fst], _iOE.snd); + if (o < 0) return -1; + return set.offsets[setIdx] + o; +} + function computeHash(set: MultiSetElements) { const { keys } = set; let hash = 23; for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.get(keys, i); + const k = OrderedSet.getAt(keys, i); hash = (31 * hash + k) | 0; hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0; } @@ -318,16 +335,15 @@ function areEqualEE(a: MultiSetElements, b: MultiSetElements) { const keys = a.keys; if (!OrderedSet.areEqual(keys, b.keys)) return false; for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.get(keys, i); + const k = OrderedSet.getAt(keys, i); if (!OrderedSet.areEqual(a[k], b[k])) return false; } return true; } - -const _aeP = IntPair.zero(); -function areIntersectingNE(a: number, b: MultiSetElements) { - IntPair.unpack(a, _aeP); +const _aeP = IntTuple.zero(); +function areIntersectingNE(a: IntTuple, b: MultiSetElements) { + IntTuple.unpack(a, _aeP); return OrderedSet.has(b.keys, _aeP.fst) && OrderedSet.has(b[_aeP.fst], _aeP.snd); } @@ -337,15 +353,15 @@ function areIntersectingEE(a: MultiSetElements, b: MultiSetElements) { if (!OrderedSet.areIntersecting(a.keys, b.keys)) return false; const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); for (let i = start; i < end; i++) { - const k = OrderedSet.get(keysA, i); + const k = OrderedSet.getAt(keysA, i); if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true; } return false; } -const _nP = IntPair.zero(); -function intersectNE(a: number, b: MultiSetElements) { - IntPair.unpack(a, _nP); +const _nP = IntTuple.zero(); +function intersectNE(a: IntTuple, b: MultiSetElements) { + IntTuple.unpack(a, _nP); return OrderedSet.has(b.keys, _nP.fst) && OrderedSet.has(b[_nP.fst], _nP.snd) ? a : MultiSet.Empty; } @@ -358,7 +374,7 @@ function intersectEE(a: MultiSetElements, b: MultiSetElements) { const keys = [], ret = Object.create(null); for (let i = start; i < end; i++) { - const k = OrderedSet.get(keysA, i); + const k = OrderedSet.getAt(keysA, i); if (OrderedSet.has(keysB, k)) { const intersection = OrderedSet.intersect(a[k], b[k]); if (OrderedSet.size(intersection) > 0) { @@ -370,16 +386,16 @@ function intersectEE(a: MultiSetElements, b: MultiSetElements) { return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); } -const _sNE = IntPair.zero(); -function subtractNE(a: number, b: MultiSetElements) { - IntPair.unpack(a, _sNE); +const _sNE = IntTuple.zero(); +function subtractNE(a: IntTuple, b: MultiSetElements) { + IntTuple.unpack(a, _sNE); return OrderedSet.has(b.keys, _sNE.fst) && OrderedSet.has(b[_sNE.fst], _sNE.snd) ? MultiSet.Empty : a; } -const _sEN = IntPair.zero(); -function subtractEN(a: MultiSetElements, b: number): MultiSet { +const _sEN = IntTuple.zero(); +function subtractEN(a: MultiSetElements, b: IntTuple): MultiSet { const aKeys = a.keys; - IntPair.unpack(b, _sEN); + IntTuple.unpack(b, _sEN); if (!OrderedSet.has(aKeys, _sEN.fst) || !OrderedSet.has(a[_sEN.fst], _sEN.snd)) return a; const set = a[_sEN.fst]; if (OrderedSet.size(set) === 1) { @@ -390,7 +406,7 @@ function subtractEN(a: MultiSetElements, b: number): MultiSet { } function subtractEE(a: MultiSetElements, b: MultiSetElements) { - if (a === b) return a; + if (a === b) return MultiSet.Empty; const keysA = a.keys, keysB = b.keys; if (!OrderedSet.areIntersecting(a.keys, b.keys)) return MultiSet.Empty; @@ -398,12 +414,12 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { const keys = [], ret = Object.create(null); for (let i = 0; i < start; i++) { - const k = OrderedSet.get(keysA, i); + const k = OrderedSet.getAt(keysA, i); keys[keys.length] = k; ret[k] = a[k]; } for (let i = start; i < end; i++) { - const k = OrderedSet.get(keysA, i); + const k = OrderedSet.getAt(keysA, i); if (OrderedSet.has(keysB, k)) { const subtraction = OrderedSet.subtract(a[k], b[k]); if (OrderedSet.size(subtraction) > 0) { @@ -416,7 +432,7 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { } } for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) { - const k = OrderedSet.get(keysA, i); + const k = OrderedSet.getAt(keysA, i); keys[keys.length] = k; ret[k] = a[k]; } @@ -426,7 +442,7 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { function findUnion(sets: ArrayLike<MultiSet>) { if (!sets.length) return MultiSet.Empty; if (sets.length === 1) return sets[0]; - if (sets.length === 2 && sets[0] === sets[1]) return sets[0]; + if (sets.length === 2 && MultiSet.areEqual(sets[0], sets[1])) return sets[0]; const eCount = { count: 0 }; const ns = unionN(sets, eCount); @@ -434,11 +450,11 @@ function findUnion(sets: ArrayLike<MultiSet>) { const ret = Object.create(null); for (let i = 0, _i = sets.length; i < _i; i++) { const s = sets[i]; - if (typeof s !== 'number') unionInto(ret, s); + if (typeof s !== 'number') unionInto(ret, s as MultiSetElements); } if (MultiSet.size(ns) > 0) { if (typeof ns === 'number') unionIntoN(ret, ns); - else unionInto(ret, ns); + else unionInto(ret, ns as MultiSetElements); } return ofObject(ret); } @@ -451,29 +467,29 @@ function unionN(sets: ArrayLike<MultiSet>, eCount: { count: number }) { } eCount.count = countE; if (!countN) return MultiSet.Empty; - if (countN === sets.length) return ofPackedPairs(sets as ArrayLike<number>); + if (countN === sets.length) return ofTuples(sets as ArrayLike<number>); const packed = new Float64Array(countN); let offset = 0; for (let i = 0, _i = sets.length; i < _i; i++) { const s = sets[i]; if (typeof s === 'number') packed[offset++] = s; } - return ofPackedPairs(packed); + return ofTuples(packed); } function unionInto(data: { [key: number]: OrderedSet }, a: MultiSetElements) { const keys = a.keys; for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.get(keys, i); + const k = OrderedSet.getAt(keys, i); const set = data[k]; if (set) data[k] = OrderedSet.union(set, a[k]); else data[k] = a[k]; } } -const _uIN = IntPair.zero(); -function unionIntoN(data: { [key: number]: OrderedSet }, a: number) { - IntPair.unpack(a, _uIN); +const _uIN = IntTuple.zero(); +function unionIntoN(data: { [key: number]: OrderedSet }, a: IntTuple) { + IntTuple.unpack(a, _uIN); const set = data[_uIN.fst]; if (set) { data[_uIN.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(_uIN.snd)); diff --git a/src/structure/collections/ordered-set.ts b/src/structure/collections/ordered-set.ts index 0aa5ca57eec081fdf755591bc5aba13927b45e50..a1d5e2e2d197593d56c3b4a046551217386cc6af 100644 --- a/src/structure/collections/ordered-set.ts +++ b/src/structure/collections/ordered-set.ts @@ -4,135 +4,156 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import IntPair from './int-pair' +import IntTuple from './int-tuple' import { hash3, hash4 } from './hash-functions' -type Nums = ArrayLike<number> -type OrderedSet = number | Nums - /** An immutable ordered set. */ +interface OrderedSet { '@type': 'int-ordered-set' } + namespace OrderedSet { - export function ofSingleton(value: number): OrderedSet { return IntPair.pack1(value, value); } - export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : IntPair.pack1(min, max); } + export const Empty: OrderedSet = IntTuple.pack(0, -1) as any; + export function ofSingleton(value: number): OrderedSet { return IntTuple.pack(value, value) as any; } + export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : IntTuple.pack(min, max) as any; } /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ - export function ofSortedArray(xs: Nums): OrderedSet { + export function ofSortedArray(xs: SortedArray): OrderedSet { if (!xs.length) return Empty; // check if the array is just a range if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]); - return xs; - } - export const Empty: OrderedSet = IntPair.pack1(0, -1); - - export function size(set: OrderedSet) { return typeof set === 'number' ? sizeR(set) : set.length; } - export function has(set: OrderedSet, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set, x); } - export function indexOf(set: OrderedSet, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set, x); } - export function get(set: OrderedSet, i: number) { return typeof set === 'number' ? elementAtR(set, i) : set[i]; } - export function min(set: OrderedSet) { return typeof set === 'number' ? minR(set) : set[0]; } - export function max(set: OrderedSet) { return typeof set === 'number' ? maxR(set) : set[set.length - 1]; } - - export function hashCode(set: OrderedSet) { - // hash of tuple (size, min, max, mid) - const s = size(set); - if (!s) return 0; - if (s > 2) return hash4(s, get(set, 0), get(set, s - 1), get(set, s >> 1)); - return hash3(s, get(set, 0), get(set, s - 1)); - } - // TODO: possibly add more hash functions to allow for multilevel hashing. - - export function areEqual(a: OrderedSet, b: OrderedSet) { - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b); - return false; - } else if (typeof b === 'number') return false; - else if (a === b) return true; - return equalAA(a, b); + return xs as any; } - export function areIntersecting(a: OrderedSet, b: OrderedSet) { - // if at least one is "range", they must now intersect - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b) || areRangesIntersecting(a, b); - return areRangesIntersecting(a, b); - } - if (!areRangesIntersecting(a, b)) return false; - else if (typeof b === 'number') return false; - if (a === b) return true; - return areIntersectingAA(a, b); - } + export const size: (set: OrderedSet) => number = sizeI as any; + export const has: (set: OrderedSet, x: number) => boolean = hasI as any; + export const indexOf: (set: OrderedSet, x: number) => number = indexOfI as any; + export const getAt: (set: OrderedSet, i: number) => number = getAtI as any; + export const min: (set: OrderedSet) => number = minI as any; + export const max: (set: OrderedSet) => number = maxI as any; + export const hashCode: (set: OrderedSet) => number = hashCodeI as any; - /** Check if the 2nd argument is a subset of the 1st */ - export function isSubset(set: OrderedSet, toTest: OrderedSet) { - if (set === toTest) return true; - if (!isRangeSubset(set, toTest)) return false; - const testSize = size(toTest); - if (typeof set === 'number' || !testSize) return true; - if (typeof toTest === 'number') return indexOf(set, maxR(toTest)) - indexOf(set, minR(toTest)) + 1 === testSize; - return isSubsetAA(set, toTest); - } + export const areEqual: BinaryTest = areEqualI as any; + export const areIntersecting: BinaryTest = areIntersectingI as any; + export const isSubset: BinaryTest = isSubsetI as any; - export function getInsertionIndex(set: OrderedSet, x: number) { - return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchIndex(set, x); - } + export const union: BinaryOp = unionI as any; + export const intersect: BinaryOp = intersectI as any; + export const subtract: BinaryOp = subtractI as any; - export function getIntervalRange(set: OrderedSet, min: number, max: number) { - const { start, end } = getStartEnd(set, min, max); - return { start, end }; - } + export const getInsertionIndex: (set: OrderedSet, x: number) => number = getInsertionIndexI as any; + export const getIntervalRange: (set: OrderedSet, min: number, max: number) => { start: number, end: number } = getIntervalRangeI as any; +} - export function union(a: OrderedSet, b: OrderedSet) { - if (a === b) return a; - if (typeof a === 'number') { - if (typeof b === 'number') return unionRR(a, b); - return unionAR(b, a); - } else if (typeof b === 'number') { - return unionAR(a, b); - } else return unionAA(a, b); - } +export default OrderedSet - export function intersect(a: OrderedSet, b: OrderedSet) { - if (a === b) return a; - if (typeof a === 'number') { - if (typeof b === 'number') return intersectRR(a, b); - return intersectAR(b, a); - } else if (typeof b === 'number') { - return intersectAR(a, b); - } else { - if (!areRangesIntersecting(a, b)) return Empty; - return intersectAA(a, b); - } +/** Long and painful implementation starts here */ + +type BinaryTest = (a: OrderedSet, b: OrderedSet) => boolean +type BinaryOp = (a: OrderedSet, b: OrderedSet) => OrderedSet + +type Range = IntTuple +type SortedArray = ArrayLike<number> +type OrderedSetImpl = Range | SortedArray + +function sizeI(set: OrderedSetImpl) { return typeof set === 'number' ? sizeR(set) : (set as SortedArray).length; } +function hasI(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set as SortedArray, x); } +function indexOfI(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set as SortedArray, x); } +function getAtI(set: OrderedSetImpl, i: number) { return typeof set === 'number' ? elementAtR(set, i) : (set as SortedArray)[i]; } +function minI(set: OrderedSetImpl) { return typeof set === 'number' ? minR(set) : (set as SortedArray)[0]; } +function maxI(set: OrderedSetImpl) { return typeof set === 'number' ? maxR(set) : (set as SortedArray)[(set as SortedArray).length - 1]; } + +function hashCodeI(set: OrderedSetImpl) { + // hash of tuple (size, min, max, mid) + const s = sizeI(set); + if (!s) return 0; + if (s > 2) return hash4(s, getAtI(set, 0), getAtI(set, s - 1), getAtI(set, s >> 1)); + return hash3(s, getAtI(set, 0), getAtI(set, s - 1)); +} +// TODO: possibly add more hash functions to allow for multilevel hashing. + +function areEqualI(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return equalRR(a, b); + return false; + } else if (typeof b === 'number') return false; + return equalAA(a as SortedArray, b as SortedArray); +} + +function areIntersectingI(a: OrderedSetImpl, b: OrderedSetImpl) { + // if at least one is "range", they must now intersect + if (typeof a === 'number') { + if (typeof b === 'number') return equalRR(a, b) || areRangesIntersecting(a, b); + return areRangesIntersecting(a, b); } + if (!areRangesIntersecting(a, b)) return false; + else if (typeof b === 'number') return false; + return areIntersectingAA(a as SortedArray, b as SortedArray); +} - export function subtract(a: OrderedSet, b: OrderedSet) { - if (a === b) return Empty; - if (!areRangesIntersecting(a, b)) return a; - - if (typeof a === 'number') { - if (typeof b === 'number') return substractRR(a, b); - return subtractRA(a, b); - } else if (typeof b === 'number') { - return subtractAR(a, b); - } else { - return subtractAA(a, b); - } +/** Check if the 2nd argument is a subset of the 1st */ +function isSubsetI(set: OrderedSetImpl, toTest: OrderedSetImpl) { + if (!isRangeSubset(set, toTest)) return false; + const testSize = sizeI(toTest); + if (typeof set === 'number' || !testSize) return true; + if (typeof toTest === 'number') return indexOfI(set, maxR(toTest)) - indexOfI(set, minR(toTest)) + 1 === testSize; + return isSubsetAA(set as SortedArray, toTest as SortedArray); +} + +function getInsertionIndexI(set: OrderedSetImpl, x: number) { + return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchIndex(set as SortedArray, x); +} + +function getIntervalRangeI(set: OrderedSetImpl, min: number, max: number) { + const { start, end } = getStartEnd(set, min, max); + return { start, end }; +} + +function unionI(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return unionRR(a, b); + return unionAR(b as SortedArray, a); + } else if (typeof b === 'number') { + return unionAR(a as SortedArray, b); + } else return unionAA(a as SortedArray, b as SortedArray); +} + +function intersectI(a: OrderedSetImpl, b: OrderedSetImpl) { + if (typeof a === 'number') { + if (typeof b === 'number') return intersectRR(a, b); + return intersectAR(b as SortedArray, a); + } else if (typeof b === 'number') { + return intersectAR(a as SortedArray, b); + } else { + if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; + return intersectAA(a as SortedArray, b as SortedArray); } } -import S = OrderedSet +function subtractI(a: OrderedSetImpl, b: OrderedSetImpl) { + if (!areRangesIntersecting(a, b)) return a; -const minR = IntPair.fst -const maxR = IntPair.snd -const equalRR = IntPair.areEqual + if (typeof a === 'number') { + if (typeof b === 'number') return substractRR(a, b); + return subtractRA(a, b as SortedArray); + } else if (typeof b === 'number') { + return subtractAR(a as SortedArray, b); + } else { + return subtractAA(a as SortedArray, b as SortedArray); + } +} -const _eR = IntPair.zero(); -function sizeR(set: number) { IntPair.unpack(set, _eR); return _eR.snd - _eR.fst + 1; } -function hasR(set: number, x: number) { IntPair.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd; } -function indexOfR(set: number, x: number) { IntPair.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd ? x - _eR.fst : -1; } -function elementAtR(set: number, i: number) { return IntPair.fst(set) + i; } +const minR = IntTuple.fst +const maxR = IntTuple.snd +const equalRR = IntTuple.areEqual -function hasA(set: Nums, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; } -function indexOfA(set: Nums, x: number) { return x >= set[0] && x <= set[set.length - 1] ? binarySearch(set, x) : -1; } +const _eR = IntTuple.zero(); +function sizeR(set: Range) { IntTuple.unpack(set, _eR); return _eR.snd - _eR.fst + 1; } +function hasR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd; } +function indexOfR(set: Range, x: number) { IntTuple.unpack(set, _eR); return x >= _eR.fst && x <= _eR.snd ? x - _eR.fst : -1; } +function elementAtR(set: Range, i: number) { return IntTuple.fst(set) + i; } -function binarySearch(xs: Nums, value: number) { +function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; } +function indexOfA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] ? binarySearch(set, x) : -1; } + +function binarySearch(xs: SortedArray, value: number) { let min = 0, max = xs.length - 1; while (min <= max) { if (min + 11 > max) { @@ -151,7 +172,7 @@ function binarySearch(xs: Nums, value: number) { return -1; } -function binarySearchIndex(xs: Nums, value: number) { +function binarySearchIndex(xs: SortedArray, value: number) { let min = 0, max = xs.length - 1; while (min < max) { const mid = (min + max) >> 1; @@ -164,16 +185,16 @@ function binarySearchIndex(xs: Nums, value: number) { return xs[min] >= value ? min : min + 1; } -const _rsiR = IntPair.zero(); -function rangeSearchIndex(r: number, value: number) { - IntPair.unpack(r, _rsiR); +const _rsiR = IntTuple.zero(); +function rangeSearchIndex(r: Range, value: number) { + IntTuple.unpack(r, _rsiR); if (value < _rsiR.fst) return 0; if (value > _rsiR.snd) return _rsiR.snd - _rsiR.fst + 1; return value - _rsiR.fst; } const _maxIntRangeRet = { i: 0, j: 0, endA: 0, endB: 0 }; -function getMaxIntersectionRange(xs: Nums, ys: Nums) { +function getMaxIntersectionRange(xs: SortedArray, ys: SortedArray) { const la = xs.length - 1, lb = ys.length - 1; _maxIntRangeRet.i = binarySearchIndex(xs, ys[0]); _maxIntRangeRet.j = binarySearchIndex(ys, xs[0]); @@ -184,15 +205,16 @@ function getMaxIntersectionRange(xs: Nums, ys: Nums) { const _startEndRet = { start: 0, end: 0 }; -function getStartEnd(set: OrderedSet, min: number, max: number) { - _startEndRet.start = S.getInsertionIndex(set, min); - let end = S.getInsertionIndex(set, max); - if (end < S.size(set) && S.get(set, end) === max) end++; +function getStartEnd(set: OrderedSetImpl, min: number, max: number) { + _startEndRet.start = getInsertionIndexI(set, min); + let end = getInsertionIndexI(set, max); + if (end < sizeI(set) && getAtI(set, end) === max) end++; _startEndRet.end = end; return _startEndRet; } -function equalAA(a: Nums, b: Nums) { +function equalAA(a: SortedArray, b: SortedArray) { + if (a === b) return true; let size = a.length; if (a.length !== b.length || a[0] !== b[0] || a[size - 1] !== b[size - 1]) return false; for (let i = 0; i < size; i++) { @@ -201,7 +223,9 @@ function equalAA(a: Nums, b: Nums) { return true; } -function areIntersectingAA(xs: Nums, ys: Nums) { +function areIntersectingAA(xs: SortedArray, ys: SortedArray) { + if (xs === ys) return true; + let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); while (i <= endA && j <= endB) { const x = xs[i], y = ys[j]; @@ -212,7 +236,9 @@ function areIntersectingAA(xs: Nums, ys: Nums) { return false; } -function isSubsetAA(xs: Nums, ys: Nums) { +function isSubsetAA(xs: SortedArray, ys: SortedArray) { + if (xs === ys) return true; + const lenB = ys.length; let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); // the 2nd array must be able to advance by at least lenB elements @@ -228,44 +254,45 @@ function isSubsetAA(xs: Nums, ys: Nums) { return equal === lenB; } -function areRangesIntersecting(a: OrderedSet, b: OrderedSet) { - return S.size(a) > 0 && S.size(b) > 0 && S.max(a) >= S.min(b) && S.min(a) <= S.max(b); +function areRangesIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { + return sizeI(a) > 0 && sizeI(b) > 0 && maxI(a) >= minI(b) && minI(a) <= maxI(b); } -function isRangeSubset(a: OrderedSet, b: OrderedSet) { - if (!S.size(a)) return S.size(b) === 0; - if (!S.size(b)) return true; - return S.min(a) <= S.min(b) && S.max(a) >= S.max(b); +function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) { + if (!sizeI(a)) return sizeI(b) === 0; + if (!sizeI(b)) return true; + return minI(a) <= minI(b) && maxI(a) >= maxI(b); } -function unionRR(a: number, b: number) { - const sizeA = S.size(a), sizeB = S.size(b); +function unionRR(a: Range, b: Range) { + if (IntTuple.areEqual(a, b)) return a; + + const sizeA = sizeR(a), sizeB = sizeR(b); if (!sizeA) return b; if (!sizeB) return a; const minA = minR(a), minB = minR(b); - if (areRangesIntersecting(a, b)) return S.ofRange(Math.min(minA, minB), Math.max(maxR(a), maxR(b))); + if (areRangesIntersecting(a as number, b as number)) return OrderedSet.ofRange(Math.min(minA, minB), Math.max(maxR(a), maxR(b))); let lSize, lMin, rSize, rMin; if (minR(a) < minR(b)) { lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; } else { lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; } const arr = new Int32Array(sizeA + sizeB); for (let i = 0; i < lSize; i++) arr[i] = i + lMin; for (let i = 0; i < rSize; i++) arr[i + lSize] = i + rMin; - return S.ofSortedArray(arr); + return OrderedSet.ofSortedArray(arr); } -const _uAR = IntPair.zero(); -function unionAR(a: Nums, b: number) { - const bSize = S.size(b); +const _uAR = IntTuple.zero(); +function unionAR(a: SortedArray, b: Range) { + const bSize = sizeI(b); if (!bSize) return a; // is the array fully contained in the range? if (isRangeSubset(b, a)) return b; - IntPair.unpack(b, _uAR); + IntTuple.unpack(b, _uAR); const min = _uAR.fst, max = _uAR.snd; const { start, end } = getStartEnd(a, min, max); - const size = start + (a.length - end) + bSize; - const indices = new Int32Array(size); + const indices = new Int32Array(start + (a.length - end) + bSize); let offset = 0; for (let i = 0; i < start; i++) indices[offset++] = a[i]; for (let i = min; i <= max; i++) indices[offset++] = i; @@ -274,7 +301,9 @@ function unionAR(a: Nums, b: number) { return OrderedSet.ofSortedArray(indices); } -function unionAA(a: Nums, b: Nums) { +function unionAA(a: SortedArray, b: SortedArray) { + if (a === b) return a; + const lenA = a.length, lenB = b.length; let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); @@ -321,20 +350,21 @@ function unionAA(a: Nums, b: Nums) { return OrderedSet.ofSortedArray(indices); } -const _iRA = IntPair.zero(), _iRB = IntPair.zero(); -function intersectRR(a: number, b: number) { +const _iRA = IntTuple.zero(), _iRB = IntTuple.zero(); +function intersectRR(a: Range, b: Range) { if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; + if (IntTuple.areEqual(a, b)) return a; - IntPair.unpack(a, _iRA); - IntPair.unpack(b, _iRB); + IntTuple.unpack(a, _iRA); + IntTuple.unpack(b, _iRB); return OrderedSet.ofRange(Math.max(_iRA.fst, _iRB.fst), Math.min(_iRA.snd, _iRB.snd)); } -const _iAR = IntPair.zero(); -function intersectAR(a: Nums, r: number) { - if (!S.size(r)) return OrderedSet.Empty; +const _iAR = IntTuple.zero(); +function intersectAR(a: SortedArray, r: Range) { + if (!sizeI(r)) return OrderedSet.Empty; - IntPair.unpack(r, _iAR); + IntTuple.unpack(r, _iAR); const { start, end } = getStartEnd(a, _iAR.fst, _iAR.snd); const resultSize = end - start; if (!resultSize) return OrderedSet.Empty; @@ -347,7 +377,9 @@ function intersectAR(a: Nums, r: number) { return OrderedSet.ofSortedArray(indices); } -function intersectAA(xs: Nums, ys: Nums) { +function intersectAA(xs: SortedArray, ys: SortedArray) { + if (xs === ys) return xs; + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); let i = sI, j = sJ; let resultSize = 0; @@ -374,10 +406,12 @@ function intersectAA(xs: Nums, ys: Nums) { return OrderedSet.ofSortedArray(indices); } -const _sRA = IntPair.zero(), _sRB = IntPair.zero(); -function substractRR(a: number, b: number) { - IntPair.unpack(a, _sRA); - IntPair.unpack(b, _sRB); +const _sRA = IntTuple.zero(), _sRB = IntTuple.zero(); +function substractRR(a: Range, b: Range) { + if (IntTuple.areEqual(a, b)) return OrderedSet.Empty; + + IntTuple.unpack(a, _sRA); + IntTuple.unpack(b, _sRB); if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a; // is A subset of B? ==> Empty @@ -397,9 +431,11 @@ function substractRR(a: number, b: number) { return OrderedSet.ofRange(_sRB.snd + 1, _sRA.snd); } -const _sAR = IntPair.zero(); -function subtractAR(a: Nums, r: number) { - IntPair.unpack(r, _sAR); +const _sAR = IntTuple.zero(); +function subtractAR(a: SortedArray, b: Range) { + IntTuple.unpack(b, _sAR); + + // is empty? if (_sAR.snd < _sAR.fst) return a; const min = _sAR.fst, max = _sAR.snd; @@ -413,10 +449,12 @@ function subtractAR(a: Nums, r: number) { return OrderedSet.ofSortedArray(ret); } -const _sAR1 = IntPair.zero(); -function subtractRA(r: number, b: Nums) { - IntPair.unpack(r, _sAR1); - if (_sAR1.snd < _sAR1.fst) return r; +const _sAR1 = IntTuple.zero(); +function subtractRA(a: Range, b: SortedArray) { + IntTuple.unpack(a, _sAR1); + + // is empty? + if (_sAR1.snd < _sAR1.fst) return a; const min = _sAR1.fst, max = _sAR1.snd; const rSize = max - min + 1; @@ -436,7 +474,9 @@ function subtractRA(r: number, b: Nums) { return OrderedSet.ofSortedArray(ret); } -function subtractAA(a: Nums, b: Nums) { +function subtractAA(a: SortedArray, b: SortedArray) { + if (a === b) return OrderedSet.Empty; + const lenA = a.length; let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); @@ -471,6 +511,4 @@ function subtractAA(a: Nums, b: Nums) { for (; i < lenA; i++) indices[offset++] = a[i]; return OrderedSet.ofSortedArray(indices); -} - -export default OrderedSet \ No newline at end of file +} \ No newline at end of file diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index 60978930d7aa649aad87b82e493d20dc2e6e45f9..13ad53905b0dc4def2dee5d3516fe4de2f211fde 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -5,7 +5,7 @@ */ import Iterator from '../collections/iterator' -import IntPair from '../collections/int-pair' +import IntTuple from '../collections/int-tuple' import * as Sort from '../collections/sort' import OrderedSet from '../collections/ordered-set' import LinkedIndex from '../collections/linked-index' @@ -33,11 +33,11 @@ describe('basic iterators', () => { describe('int pair', () => { it('works', () => { - const p = IntPair.zero(); + const p = IntTuple.zero(); for (let i = 0; i < 10; i++) { for (let j = -10; j < 5; j++) { - const t = IntPair.pack1(i, j); - IntPair.unpack(t, p); + const t = IntTuple.pack(i, j); + IntTuple.unpack(t, p); expect(p.fst).toBe(i); expect(p.snd).toBe(j); } @@ -131,7 +131,7 @@ describe('qsort-dual array', () => { describe('ordered set', () => { function ordSetToArray(set: OrderedSet) { const ret = []; - for (let i = 0, _i = OrderedSet.size(set); i < _i; i++) ret.push(OrderedSet.get(set, i)); + for (let i = 0, _i = OrderedSet.size(set); i < _i; i++) ret.push(OrderedSet.getAt(set, i)); return ret; } @@ -317,13 +317,13 @@ describe('linked-index', () => { }); describe('multiset', () => { - const p = (i: number, j: number) => IntPair.create(i, j); - const r = (i: number, j: number) => IntPair.pack1(i, j); + const p = (i: number, j: number) => IntTuple.create(i, j); + const r = (i: number, j: number) => IntTuple.pack(i, j); - function setToPairs(set: MultiSet): IntPair[] { + function setToPairs(set: MultiSet): ArrayLike<IntTuple.Unpacked> { const ret = []; const it = MultiSet.values(set); - for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = IntPair.create(v.fst, v.snd); + for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = IntTuple.create(v.fst, v.snd); return ret; } @@ -332,7 +332,8 @@ describe('multiset', () => { expect(setToPairs(set)).toEqual([p(10, 11)]); expect(MultiSet.has(set, r(10, 11))).toBe(true); expect(MultiSet.has(set, r(11, 11))).toBe(false); - expect(MultiSet.get(set, 0)).toBe(r(10, 11)); + expect(MultiSet.getAt(set, 0)).toBe(r(10, 11)); + expect(MultiSet.size(set)).toBe(1); }); it('singleton number', () => { @@ -352,12 +353,12 @@ describe('multiset', () => { expect(MultiSet.has(set, r(3, 0))).toBe(true); expect(MultiSet.has(set, r(1, 7))).toBe(true); for (let i = 0; i < MultiSet.size(set); i++) { - expect(MultiSet.get(set, i)).toBe(IntPair.pack(ret[i])); + expect(MultiSet.getAt(set, i)).toBe(IntTuple.pack1(ret[i])); } }); - it('element at', () => { - const control = []; + it('element at / index of', () => { + const control: IntTuple[] = []; const sets = Object.create(null); for (let i = 1; i < 10; i++) { const set = []; @@ -369,7 +370,11 @@ describe('multiset', () => { } const ms = MultiSet.create(sets); for (let i = 0; i < control.length; i++) { - expect(IntPair.areEqual(MultiSet.get(ms, i), control[i])).toBe(true); + expect(IntTuple.areEqual(MultiSet.getAt(ms, i), control[i])).toBe(true); + } + + for (let i = 0; i < control.length; i++) { + expect(MultiSet.indexOf(ms, control[i])).toBe(i); } });