diff --git a/src/perf-tests/ordered-set.ts b/src/perf-tests/ordered-set.ts index 04b54aaa33d308c3f93a2a972c9e74720a21f5c9..8086e7746e04c2bf9ddd76b59b9a7fd89e8e7608 100644 --- a/src/perf-tests/ordered-set.ts +++ b/src/perf-tests/ordered-set.ts @@ -1,34 +1,30 @@ -import * as B from 'benchmark' -import OrderedSet from '../structure/collections/ordered-set' -import IntPair from '../structure/collections/int-pair' - - -const range = OrderedSet.ofRange(0, 100); -const pairSet = IntPair.pack1(0, 100); - -namespace PairSet { - const pair = IntPair.zero(); - export function has(p: number, x: number) { - IntPair.unpack(p, pair); - return x >= pair.fst && x <= pair.snd; - } -} - -const suite = new B.Suite(); - -const values: number[] = []; -for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0; - -let idx = 0; - -suite - .add('range', () => range.has(idx % values.length)) - .add('pair', () => PairSet.has(pairSet, idx % values.length)) - .on('cycle', (e: any) => { - console.log(String(e.target)); - }) -// .run(); - -console.log(IntPair.pack1(0, -20)); -console.log(IntPair.unpack1(IntPair.pack1(0, -20))); - +// import * as B from 'benchmark' +// import OrderedSet from '../structure/collections/ordered-set' +// import OrderedSet1 from '../structure/collections/ordered-set.1' + +// const range = OrderedSet.ofRange(0, 100); +// const range1 = OrderedSet1.ofRange(0, 100); +// // const pairSet = IntPair.pack1(0, 100); + +// // namespace PairSet { +// // const pair = IntPair.zero(); +// // export function has(p: number, x: number) { +// // IntPair.unpack(p, pair); +// // return x >= pair.fst && x <= pair.snd; +// // } +// // } + +// const suite = new B.Suite(); + +// const values: number[] = []; +// for (let i = 0; i < 1000000; i++) values[i] = (Math.random() * 1000) | 0; + +// let idx = 0; + +// suite +// .add('range', () => range.has(idx % values.length)) +// .add('range1', () => OrderedSet1.has(range1, idx % values.length)) +// .on('cycle', (e: any) => { +// console.log(String(e.target)); +// }) +// .run(); diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts index 4b85f2e36d1e451f9dc30f4f413d7e99e6e59f57..c3846abdabac757b07d4250f0606d903070e037f 100644 --- a/src/structure/collections/multi-set.ts +++ b/src/structure/collections/multi-set.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import OrderedSet from './ordered-set.1' +import OrderedSet from './ordered-set' import Iterator from './iterator' import IntPair from './int-pair' import { sortArray } from './sort' @@ -30,7 +30,7 @@ namespace MultiSet { export function hasKey(set: MultiSet, key: number): boolean { if (typeof set === 'number') return IntPair.fst(set) === key; - return set.keys.has(key); + return OrderedSet.has(set.keys, key); } export function get(set: MultiSet, key: number): OrderedSet { @@ -102,6 +102,7 @@ namespace MultiSet { private pair = IntPair.zero(); private unit = 0; + private keyCount: number; private setIndex = -1; private currentIndex = 0; private currentSize = 0; @@ -125,7 +126,7 @@ namespace MultiSet { private advance() { const keys = this.elements.keys; - if (++this.setIndex >= keys.size) { + if (++this.setIndex >= this.keyCount) { this.done = true; return false; } @@ -133,12 +134,13 @@ namespace MultiSet { this.pair.fst = this.unit; this.currentSet = this.elements[this.unit]; this.currentIndex = 0; - this.currentSize = this.currentSet.size; + this.currentSize = OrderedSet.size(this.currentSet); return true; } constructor(private elements: MultiSetElements) { - this.done = elements.keys.size === 0; + this.keyCount = OrderedSet.size(elements.keys); + this.done = this.keyCount === 0; this.advance(); } } @@ -159,12 +161,12 @@ function ofObject(data: { [id: number]: OrderedSet }) { const keys = []; for (const _k of Object.keys(data)) { const k = +_k; - if (data[k].size > 0) keys[keys.length] = k; + if (OrderedSet.size(data[k]) > 0) keys[keys.length] = k; } if (!keys.length) return MultiSet.Empty; if (keys.length === 1) { const set = data[keys[0]]; - if (set.size === 1) return IntPair.pack1(keys[0], set.elementAt(0)); + if (OrderedSet.size(set) === 1) return IntPair.pack1(keys[0], OrderedSet.elementAt(set, 0)); } return ofObject1(keys, data); } @@ -173,17 +175,17 @@ function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { if (keys.length === 1) { const k = keys[0]; const set = data[k]; - if (set.size === 1) return IntPair.pack1(k, set.elementAt(0)); + if (OrderedSet.size(set) === 1) return IntPair.pack1(k, OrderedSet.elementAt(set, 0)); } sortArray(keys); return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data); } function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { - if (keys.size === 1) { - const k = keys.elementAt(0); + if (OrderedSet.size(keys) === 1) { + const k = OrderedSet.elementAt(keys, 0); const set = data[k]; - if (set.size === 1) return IntPair.pack1(k, set.elementAt(0)); + if (OrderedSet.size(set) === 1) return IntPair.pack1(k, OrderedSet.elementAt(set, 0)); } return _createObjectOrdered(keys, data); } @@ -192,11 +194,11 @@ function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet const ret: MultiSetElements = Object.create(null); ret.keys = keys; let size = 0; - for (let i = 0, _i = keys.size; i < _i; i++) { - const k = keys.elementAt(i); + for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { + const k = OrderedSet.elementAt(keys, i); const set = data[k]; ret[k] = set; - size += set.size; + size += OrderedSet.size(set); } ret.size = size; ret.hashCode = -1; @@ -248,8 +250,8 @@ function ofPackedPairs(xs: ArrayLike<number>): MultiSet { function computeHash(set: MultiSetElements) { const { keys } = set; let hash = 23; - for (let i = 0, _i = keys.size; i < _i; i++) { - const k = keys.elementAt(i); + for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { + const k = OrderedSet.elementAt(keys, i); hash = (31 * hash + k) | 0; hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0; } @@ -265,8 +267,8 @@ function areEqualEE(a: MultiSetElements, b: MultiSetElements) { const keys = a.keys; if (!OrderedSet.areEqual(keys, b.keys)) return false; - for (let i = 0, _i = keys.size; i < _i; i++) { - const k = keys.elementAt(i); + for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { + const k = OrderedSet.elementAt(keys, i); if (!OrderedSet.areEqual(a[k], b[k])) return false; } return true; @@ -274,7 +276,7 @@ function areEqualEE(a: MultiSetElements, b: MultiSetElements) { function areIntersectingNE(a: number, b: MultiSetElements) { IntPair.unpack(a, pair); - return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd); + return OrderedSet.has(b.keys, pair.fst) && OrderedSet.has(b[pair.fst], pair.snd); } function areIntersectingEE(a: MultiSetElements, b: MultiSetElements) { @@ -283,15 +285,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 = keysA.elementAt(i); - if (keysB.has(k) && OrderedSet.areIntersecting(a[k], b[k])) return true; + const k = OrderedSet.elementAt(keysA, i); + if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true; } return false; } function intersectNE(a: number, b: MultiSetElements) { IntPair.unpack(a, pair); - return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd) ? a : MultiSet.Empty; + return OrderedSet.has(b.keys, pair.fst) && OrderedSet.has(b[pair.fst], pair.snd) ? a : MultiSet.Empty; } function intersectEE(a: MultiSetElements, b: MultiSetElements) { @@ -303,10 +305,10 @@ function intersectEE(a: MultiSetElements, b: MultiSetElements) { const keys = [], ret = Object.create(null); for (let i = start; i < end; i++) { - const k = keysA.elementAt(i); - if (keysB.has(k)) { + const k = OrderedSet.elementAt(keysA, i); + if (OrderedSet.has(keysB, k)) { const intersection = OrderedSet.intersect(a[k], b[k]); - if (intersection.size > 0) { + if (OrderedSet.size(intersection) > 0) { keys[keys.length] = k; ret[k] = intersection; } @@ -317,15 +319,15 @@ function intersectEE(a: MultiSetElements, b: MultiSetElements) { function subtractNE(a: number, b: MultiSetElements) { IntPair.unpack(a, pair); - return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd) ? MultiSet.Empty : a; + return OrderedSet.has(b.keys, pair.fst) && OrderedSet.has(b[pair.fst], pair.snd) ? MultiSet.Empty : a; } function subtractEN(a: MultiSetElements, b: number): MultiSet { const aKeys = a.keys; IntPair.unpack(b, pair); - if (!aKeys.has(pair.fst) || !a[pair.fst].has(pair.snd)) return a; + if (!OrderedSet.has(aKeys, pair.fst) || !OrderedSet.has(a[pair.fst], pair.snd)) return a; const set = a[pair.fst]; - if (set.size === 1) { + if (OrderedSet.size(set) === 1) { return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(pair.fst)), a); } else { return { ...a, [pair.fst]: OrderedSet.subtract(set, OrderedSet.ofSingleton(pair.snd)), size: a.size - 1, hashCode: -1 } @@ -341,15 +343,15 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { const keys = [], ret = Object.create(null); for (let i = 0; i < start; i++) { - const k = keysA.elementAt(i); + const k = OrderedSet.elementAt(keysA, i); keys[keys.length] = k; ret[k] = a[k]; } for (let i = start; i < end; i++) { - const k = keysA.elementAt(i); - if (keysB.has(k)) { + const k = OrderedSet.elementAt(keysA, i); + if (OrderedSet.has(keysB, k)) { const subtraction = OrderedSet.subtract(a[k], b[k]); - if (subtraction.size > 0) { + if (OrderedSet.size(subtraction) > 0) { keys[keys.length] = k; ret[k] = subtraction; } @@ -358,8 +360,8 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { ret[k] = a[k]; } } - for (let i = end, _i = keysA.size; i < _i; i++) { - const k = keysA.elementAt(i); + for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) { + const k = OrderedSet.elementAt(keysA, i); keys[keys.length] = k; ret[k] = a[k]; } @@ -406,8 +408,8 @@ function unionN(sets: ArrayLike<MultiSet>, eCount: { count: number }) { function unionInto(data: { [key: number]: OrderedSet }, a: MultiSetElements) { const keys = a.keys; - for (let i = 0, _i = keys.size; i < _i; i++) { - const k = keys.elementAt(i); + for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { + const k = OrderedSet.elementAt(keys, i); const set = data[k]; if (set) data[k] = OrderedSet.union(set, a[k]); else data[k] = a[k]; diff --git a/src/structure/collections/ordered-set.1.ts b/src/structure/collections/ordered-set.1.ts deleted file mode 100644 index cab641230f439cedf9832ac6bb02819630f07bb7..0000000000000000000000000000000000000000 --- a/src/structure/collections/ordered-set.1.ts +++ /dev/null @@ -1,476 +0,0 @@ -/** - * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import IntPair from './int-pair' -import { hash3, hash4 } from './hash-functions' - -type Nums = ArrayLike<number> -type OrderedSet = number | Nums - -/** An immutable 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); } - /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ - export function ofSortedArray(xs: Nums): 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 elementAt(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, elementAt(set, 0), elementAt(set, s - 1), elementAt(set, s >> 1)); - return hash3(s, elementAt(set, 0), elementAt(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); - } - - 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); - } - - /** 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 function getInsertionIndex(set: OrderedSet, x: number) { - return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchIndex(set, x); - } - - export function getIntervalRange(set: OrderedSet, min: number, max: number) { - const { start, end } = getStartEnd(set, min, max); - return { start, end }; - } - - 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 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); - } - } - - 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); - } - } -} - -import S = OrderedSet - -const minR = IntPair.fst -const maxR = IntPair.snd -const equalRR = IntPair.areEqual - -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; } - -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; } - -function binarySearch(xs: Nums, value: number) { - let min = 0, max = xs.length - 1; - while (min <= max) { - if (min + 11 > max) { - for (let i = min; i <= max; i++) { - if (value === xs[i]) return i; - } - return -1; - } - - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - return -1; -} - -function binarySearchIndex(xs: Nums, value: number) { - let min = 0, max = xs.length - 1; - while (min < max) { - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - if (min > max) return max + 1; - return xs[min] >= value ? min : min + 1; -} - -const _rsiR = IntPair.zero(); -function rangeSearchIndex(r: number, value: number) { - IntPair.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) { - const la = xs.length - 1, lb = ys.length - 1; - _maxIntRangeRet.i = binarySearchIndex(xs, ys[0]); - _maxIntRangeRet.j = binarySearchIndex(ys, xs[0]); - _maxIntRangeRet.endA = Math.min(binarySearchIndex(xs, ys[lb]), la); - _maxIntRangeRet.endB = Math.min(binarySearchIndex(ys, xs[la]), lb); - return _maxIntRangeRet; -} - -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.elementAt(set, end) === max) end++; - _startEndRet.end = end; - return _startEndRet; -} - -function equalAA(a: Nums, b: Nums) { - 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++) { - if (a[i] !== b[i]) return false; - } - return true; -} - -function areIntersectingAA(xs: Nums, ys: Nums) { - let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); - while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else return true; - } - return false; -} - -function isSubsetAA(xs: Nums, ys: Nums) { - 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 - if (endB - j + 1 < lenB || endA - j + 1 < lenB) return false; - - let equal = 0; - while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; equal++; } - } - 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 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 unionRR(a: number, b: number) { - const sizeA = S.size(a), sizeB = S.size(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))); - 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); -} - -const _uAR = IntPair.zero(); -function unionAR(a: Nums, b: number) { - const bSize = S.size(b); - if (!bSize) return a; - // is the array fully contained in the range? - if (isRangeSubset(b, a)) return b; - - IntPair.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); - let offset = 0; - for (let i = 0; i < start; i++) indices[offset++] = a[i]; - for (let i = min; i <= max; i++) indices[offset++] = i; - for (let i = end, _i = a.length; i < _i; i++) indices[offset] = a[i]; - - return OrderedSet.ofSortedArray(indices); -} - -function unionAA(a: Nums, b: Nums) { - const lenA = a.length, lenB = b.length; - - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - if (!commonCount) return a; - if (commonCount >= lenA) return OrderedSet.Empty - - const resultSize = lenA + lenB - commonCount; - const l = Math.min(a[0], b[0]), r = Math.max(a[lenA - 1], b[lenB - 1]); - // is this just a range? - if (resultSize === r - l + 1) { - return OrderedSet.ofRange(l, r); - } - - const indices = new Int32Array(lenA + lenB - commonCount); - let offset = 0; - - // insert the "prefixes" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - for (let k = 0; k < sJ; k++) indices[offset++] = a[k]; - - // insert the common part - i = sI; - j = sJ; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { indices[offset++] = y; j++; } - else { indices[offset++] = x; i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; - for (; j < lenB; j++) indices[offset++] = b[j]; - - return OrderedSet.ofSortedArray(indices); -} - -const _iRA = IntPair.zero(), _iRB = IntPair.zero(); -function intersectRR(a: number, b: number) { - if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; - - IntPair.unpack(a, _iRA); - IntPair.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; - - IntPair.unpack(r, _iAR); - const { start, end } = getStartEnd(a, _iAR.fst, _iAR.snd); - const resultSize = end - start; - if (!resultSize) return OrderedSet.Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - for (let i = start; i < end; i++) { - indices[offset++] = a[i]; - } - return OrderedSet.ofSortedArray(indices); -} - -function intersectAA(xs: Nums, ys: Nums) { - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); - let i = sI, j = sJ; - let resultSize = 0; - while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; resultSize++; } - } - - if (!resultSize) return OrderedSet.Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - i = sI; - j = sJ; - while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = x; i++; j++; } - } - - 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); - - if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a; - // is A subset of B? ==> Empty - if (isRangeSubset(b, a)) return OrderedSet.Empty; - if (isRangeSubset(a, b)) { - // this splits the interval into two, gotta represent it as a set. - const l = _sRB.fst - _sRA.fst, r = _sRA.snd - _sRB.snd; - const ret = new Int32Array(l + r); - let offset = 0; - for (let i = 0; i < l; i++) ret[offset++] = _sRA.fst + i; - for (let i = 1; i <= r; i++) ret[offset++] = _sRB.snd + i; - return OrderedSet.ofSortedArray(ret); - } - // non intersecting ranges are handled by top-level substract. - // at this point, b either contains rA.fst or rA.snd, but not both. - if (_sRA.fst < _sRB.fst) return OrderedSet.ofRange(_sRA.fst, _sRB.fst - 1); - return OrderedSet.ofRange(_sRB.snd + 1, _sRA.snd); -} - -const _sAR = IntPair.zero(); -function subtractAR(a: Nums, r: number) { - IntPair.unpack(r, _sAR); - if (_sAR.snd < _sAR.fst) return a; - - const min = _sAR.fst, max = _sAR.snd; - const { start, end } = getStartEnd(a, min, max); - const size = a.length - (end - start); - if (size <= 0) return OrderedSet.Empty; - const ret = new Int32Array(size); - let offset = 0; - for (let i = 0; i < start; i++) ret[offset++] = a[i]; - for (let i = end, _i = a.length; i < _i; i++) ret[offset++] = a[i]; - 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 min = _sAR1.fst, max = _sAR1.snd; - const rSize = max - min + 1; - const { start, end } = getStartEnd(b, min, max); - const commonCount = end - start; - const resultSize = rSize - commonCount; - if (resultSize <= 0) return OrderedSet.Empty; - const ret = new Int32Array(resultSize); - const li = b.length - 1; - const fst = b[Math.min(start, li)], last = b[Math.min(end, li)]; - let offset = 0; - for (let i = min; i < fst; i++) ret[offset++] = i; - for (let i = fst; i <= last; i++) { - if (binarySearch(b, i) < 0) ret[offset++] = i; - } - for (let i = last + 1; i <= max; i++) ret[offset++] = i; - return OrderedSet.ofSortedArray(ret); -} - -function subtractAA(a: Nums, b: Nums) { - const lenA = a.length; - - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - if (!commonCount) return a; - if (commonCount >= lenA) return OrderedSet.Empty; - - const indices = new Int32Array(lenA - commonCount); - let offset = 0; - - // insert the "prefix" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - - i = sI; - j = sJ; - while (i <= endA && j <= endB) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { j++; } - else { i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; - - return OrderedSet.ofSortedArray(indices); -} - -export default OrderedSet \ No newline at end of file diff --git a/src/structure/collections/ordered-set.ts b/src/structure/collections/ordered-set.ts index 2a24e2c869c0b30d7f3df48a9d54cb09fd5a49fb..cab641230f439cedf9832ac6bb02819630f07bb7 100644 --- a/src/structure/collections/ordered-set.ts +++ b/src/structure/collections/ordered-set.ts @@ -4,127 +4,101 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +import IntPair from './int-pair' import { hash3, hash4 } from './hash-functions' -/** An immutable ordered set. */ -interface OrderedSet { - readonly size: number, - has(x: number): boolean, - indexOf(x: number): number, - elementAt(i: number): number -} - -interface Impl extends OrderedSet { - readonly min: number, - readonly max: number -} - -class RangeImpl implements Impl { - size: number; - has(x: number) { return x >= this.min && x <= this.max; } - indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; } - elementAt(i: number) { return this.min + i; } - - constructor(public min: number, public max: number) { - this.size = max - min + 1; - } -} - -class ArrayImpl implements Impl { - size: number; - public min: number; - public max: number; - has(x: number) { return x >= this.min && x <= this.max && binarySearch(this.values, x) >= 0; } - indexOf(x: number) { return x >= this.min && x <= this.max ? binarySearch(this.values, x) : -1; } - elementAt(i: number) { return this.values[i]; } - - constructor(public values: ArrayLike<number>) { - this.min = values[0]; - this.max = values[values.length - 1]; - this.size = values.length; - } -} +type Nums = ArrayLike<number> +type OrderedSet = number | Nums +/** An immutable ordered set. */ namespace OrderedSet { - export function ofSingleton(value: number): OrderedSet { return new RangeImpl(value, value); } - export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : new RangeImpl(min, max); } + 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); } /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ - export function ofSortedArray(xs: ArrayLike<number>): OrderedSet { + export function ofSortedArray(xs: Nums): 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 new ArrayImpl(xs); + return xs; } - export const Empty: OrderedSet = new RangeImpl(0, -1); + export const Empty: OrderedSet = IntPair.pack1(0, -1); - export function isEmpty(a: OrderedSet) { return a.size === 0; } - export function min(a: OrderedSet) { return (a as Impl).min; } - export function max(a: OrderedSet) { return (a as Impl).max; } + 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 elementAt(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(a: OrderedSet) { + export function hashCode(set: OrderedSet) { // hash of tuple (size, min, max, mid) - const { size } = a; - if (!size) return 0; - if (size > 2) return hash4(size, a.elementAt(0), a.elementAt(size - 1), a.elementAt(size >> 0)); - return hash3(size, a.elementAt(0), a.elementAt(size - 1)); + const s = size(set); + if (!s) return 0; + if (s > 2) return hash4(s, elementAt(set, 0), elementAt(set, s - 1), elementAt(set, s >> 1)); + return hash3(s, elementAt(set, 0), elementAt(set, s - 1)); } // TODO: possibly add more hash functions to allow for multilevel hashing. export function areEqual(a: OrderedSet, b: OrderedSet) { - 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); + 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); } export function areIntersecting(a: OrderedSet, b: OrderedSet) { - if (a === b) return true; - if (!areRangesIntersecting(a, b)) return false; // if at least one is "range", they must now intersect - if (a instanceof RangeImpl || b instanceof RangeImpl) return true; - return areIntersectingAA((a as ArrayImpl).values, (b as ArrayImpl).values); + 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); } /** Check if the 2nd argument is a subset of the 1st */ - export function isSubset(a: OrderedSet, toTest: OrderedSet) { - if (a === toTest) return true; - if (!isRangeSubset(a, toTest)) return false; - if (!toTest.size || a instanceof RangeImpl) return true; - if (toTest instanceof RangeImpl) return a.indexOf(max(toTest)) - a.indexOf(min(toTest)) + 1 === toTest.size; - return isSubsetAA((a as ArrayImpl).values, (toTest as ArrayImpl).values); + 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 function getInsertionIndex(set: OrderedSet, x: number) { + return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchIndex(set, x); } - export function getIntervalRange(a: OrderedSet, min: number, max: number) { - const { start, end } = a instanceof RangeImpl - ? getStartEndR(a, min, max) - : getStartEndA((a as ArrayImpl).values, min, max); + export function getIntervalRange(set: OrderedSet, min: number, max: number) { + const { start, end } = getStartEnd(set, min, max); return { start, end }; } export function union(a: OrderedSet, b: OrderedSet) { if (a === b) return a; - if (a instanceof RangeImpl) { - if (b instanceof RangeImpl) return unionRR(a, b); - return unionAR(b as ArrayImpl, a); - } else if (b instanceof RangeImpl) { - return unionAR(a as ArrayImpl, b); - } else return unionAA(a as ArrayImpl, b as ArrayImpl); + 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 function intersect(a: OrderedSet, b: OrderedSet) { if (a === b) return a; - if (a instanceof RangeImpl) { - if (b instanceof RangeImpl) return intersectRR(a, b); - return intersectAR(b as ArrayImpl, a); - } else if (b instanceof RangeImpl) { - return intersectAR(a as ArrayImpl, b); + 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 as ArrayImpl).values, (b as ArrayImpl).values); + return intersectAA(a, b); } } @@ -132,21 +106,33 @@ namespace OrderedSet { if (a === b) return Empty; if (!areRangesIntersecting(a, b)) return a; - if (a instanceof RangeImpl) { - if (b instanceof RangeImpl) return substractRR(a, b); - return subtractRA(a, (b as ArrayImpl).values); - } else if (b instanceof RangeImpl) { - return subtractAR(a as ArrayImpl, b); + 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 as ArrayImpl, b as ArrayImpl); + return subtractAA(a, b); } } } -function min(a: OrderedSet) { return (a as Impl).min; } -function max(a: OrderedSet) { return (a as Impl).max; } +import S = OrderedSet + +const minR = IntPair.fst +const maxR = IntPair.snd +const equalRR = IntPair.areEqual + +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; } -function binarySearch(xs: ArrayLike<number>, value: number) { +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; } + +function binarySearch(xs: Nums, value: number) { let min = 0, max = xs.length - 1; while (min <= max) { if (min + 11 > max) { @@ -165,7 +151,7 @@ function binarySearch(xs: ArrayLike<number>, value: number) { return -1; } -function binarySearchIndex(xs: ArrayLike<number>, value: number) { +function binarySearchIndex(xs: Nums, value: number) { let min = 0, max = xs.length - 1; while (min < max) { const mid = (min + max) >> 1; @@ -178,8 +164,16 @@ function binarySearchIndex(xs: ArrayLike<number>, value: number) { return xs[min] >= value ? min : min + 1; } +const _rsiR = IntPair.zero(); +function rangeSearchIndex(r: number, value: number) { + IntPair.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: ArrayLike<number>, ys: ArrayLike<number>) { +function getMaxIntersectionRange(xs: Nums, ys: Nums) { const la = xs.length - 1, lb = ys.length - 1; _maxIntRangeRet.i = binarySearchIndex(xs, ys[0]); _maxIntRangeRet.j = binarySearchIndex(ys, xs[0]); @@ -190,40 +184,24 @@ function getMaxIntersectionRange(xs: ArrayLike<number>, ys: ArrayLike<number>) { const _startEndRet = { start: 0, end: 0 }; -function getStartEndR(r: RangeImpl, min: number, max: number) { - if (max < min) { - _startEndRet.start = 0; - _startEndRet.end = 0; - return _startEndRet; - } - _startEndRet.start = min <= r.max ? Math.max(r.min, min) - r.min : r.size; - _startEndRet.end = max >= r.min ? Math.min(r.max, max) - r.min + 1 : r.size; - return _startEndRet; -} - -function getStartEndA(xs: ArrayLike<number>, min: number, max: number) { - _startEndRet.start = binarySearchIndex(xs, min); - let end = binarySearchIndex(xs, max); - if (xs[end] === max) end++; +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.elementAt(set, end) === max) end++; _startEndRet.end = end; return _startEndRet; } -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; +function equalAA(a: Nums, b: Nums) { + 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++) { - if (xs[i] !== ys[i]) return false; + if (a[i] !== b[i]) return false; } return true; } -function areIntersectingAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { +function areIntersectingAA(xs: Nums, ys: Nums) { let { i, j, endA, endB } = getMaxIntersectionRange(xs, ys); while (i <= endA && j <= endB) { const x = xs[i], y = ys[j]; @@ -234,7 +212,7 @@ function areIntersectingAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { return false; } -function isSubsetAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { +function isSubsetAA(xs: Nums, ys: Nums) { 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 @@ -251,56 +229,59 @@ function isSubsetAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { } function areRangesIntersecting(a: OrderedSet, b: OrderedSet) { - return a.size > 0 && b.size > 0 && max(a) >= min(b) && min(a) <= max(b); + return S.size(a) > 0 && S.size(b) > 0 && S.max(a) >= S.min(b) && S.min(a) <= S.max(b); } function isRangeSubset(a: OrderedSet, b: OrderedSet) { - if (!a.size) return b.size === 0; - if (!b.size) return true; - return min(a) <= min(b) && max(a) >= max(b); + 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 unionRR(a: RangeImpl, b: RangeImpl) { - if (!a.size) return b; - if (!b.size) return a; - if (areRangesIntersecting(a, b)) return OrderedSet.ofRange(Math.min(a.min, b.min), Math.max(a.max, b.max)); - let l, r; - if (a.min < b.min) { l = a; r = b; } - else { l = b; r = a; } - const arr = new Int32Array(a.size + b.size); - for (let i = 0; i < l.size; i++) arr[i] = i + l.min; - for (let i = 0; i < r.size; i++) arr[i + l.size] = i + r.min; - return OrderedSet.ofSortedArray(arr); +function unionRR(a: number, b: number) { + const sizeA = S.size(a), sizeB = S.size(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))); + 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); } -function unionAR(a: ArrayImpl, b: RangeImpl) { - if (!b.size) return a; +const _uAR = IntPair.zero(); +function unionAR(a: Nums, b: number) { + const bSize = S.size(b); + if (!bSize) return a; // is the array fully contained in the range? - if (a.min >= b.min && a.max <= b.max) return b; + if (isRangeSubset(b, a)) return b; - const xs = a.values; - const { min, max } = b; - const { start, end } = getStartEndA(xs, min, max); + IntPair.unpack(b, _uAR); + const min = _uAR.fst, max = _uAR.snd; + const { start, end } = getStartEnd(a, min, max); - const size = start + (xs.length - end) + b.size; + const size = start + (a.length - end) + bSize; const indices = new Int32Array(size); let offset = 0; - for (let i = 0; i < start; i++) indices[offset++] = xs[i]; + for (let i = 0; i < start; i++) indices[offset++] = a[i]; for (let i = min; i <= max; i++) indices[offset++] = i; - for (let i = end, _i = xs.length; i < _i; i++) indices[offset] = xs[i]; + for (let i = end, _i = a.length; i < _i; i++) indices[offset] = a[i]; return OrderedSet.ofSortedArray(indices); } -function unionAA(a: ArrayImpl, b: ArrayImpl) { - const xs = a.values, ys = b.values; - const lenA = xs.length, lenB = ys.length; +function unionAA(a: Nums, b: Nums) { + const lenA = a.length, lenB = b.length; - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); let i = sI, j = sJ; let commonCount = 0; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { i++; } else if (x > y) { j++; } else { i++; j++; commonCount++; } @@ -310,7 +291,7 @@ function unionAA(a: ArrayImpl, b: ArrayImpl) { if (commonCount >= lenA) return OrderedSet.Empty const resultSize = lenA + lenB - commonCount; - const l = Math.min(min(a), min(b)), r = Math.max(max(a), max(b)); + const l = Math.min(a[0], b[0]), r = Math.max(a[lenA - 1], b[lenB - 1]); // is this just a range? if (resultSize === r - l + 1) { return OrderedSet.ofRange(l, r); @@ -320,48 +301,53 @@ function unionAA(a: ArrayImpl, b: ArrayImpl) { let offset = 0; // insert the "prefixes" - for (let k = 0; k < sI; k++) indices[offset++] = xs[k]; - for (let k = 0; k < sJ; k++) indices[offset++] = xs[k]; + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; + for (let k = 0; k < sJ; k++) indices[offset++] = a[k]; // insert the common part i = sI; j = sJ; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { indices[offset++] = x; i++; } else if (x > y) { indices[offset++] = y; j++; } else { indices[offset++] = x; i++; j++; } } // insert the "tail" - for (; i < lenA; i++) indices[offset++] = xs[i]; - for (; j < lenB; j++) indices[offset++] = ys[j]; + for (; i < lenA; i++) indices[offset++] = a[i]; + for (; j < lenB; j++) indices[offset++] = b[j]; return OrderedSet.ofSortedArray(indices); } -function intersectRR(a: RangeImpl, b: RangeImpl) { +const _iRA = IntPair.zero(), _iRB = IntPair.zero(); +function intersectRR(a: number, b: number) { if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; - return OrderedSet.ofRange(Math.max(a.min, b.min), Math.min(a.max, b.max)); + + IntPair.unpack(a, _iRA); + IntPair.unpack(b, _iRB); + return OrderedSet.ofRange(Math.max(_iRA.fst, _iRB.fst), Math.min(_iRA.snd, _iRB.snd)); } -function intersectAR(a: ArrayImpl, r: RangeImpl) { - if (!r.size) return OrderedSet.Empty; +const _iAR = IntPair.zero(); +function intersectAR(a: Nums, r: number) { + if (!S.size(r)) return OrderedSet.Empty; - const xs = a.values; - const { start, end } = getStartEndA(xs, r.min, r.max); + IntPair.unpack(r, _iAR); + const { start, end } = getStartEnd(a, _iAR.fst, _iAR.snd); const resultSize = end - start; if (!resultSize) return OrderedSet.Empty; const indices = new Int32Array(resultSize); let offset = 0; for (let i = start; i < end; i++) { - indices[offset++] = xs[i]; + indices[offset++] = a[i]; } return OrderedSet.ofSortedArray(indices); } -function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { +function intersectAA(xs: Nums, ys: Nums) { let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); let i = sI, j = sJ; let resultSize = 0; @@ -388,69 +374,76 @@ function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { return OrderedSet.ofSortedArray(indices); } -function substractRR(a: RangeImpl, b: RangeImpl) { - if (a.size === 0 || b.size === 0) return a; +const _sRA = IntPair.zero(), _sRB = IntPair.zero(); +function substractRR(a: number, b: number) { + IntPair.unpack(a, _sRA); + IntPair.unpack(b, _sRB); + + if (_sRA.snd < _sRA.fst || _sRB.snd < _sRB.fst) return a; // is A subset of B? ==> Empty if (isRangeSubset(b, a)) return OrderedSet.Empty; if (isRangeSubset(a, b)) { // this splits the interval into two, gotta represent it as a set. - const l = b.min - a.min, r = a.max - b.max; + const l = _sRB.fst - _sRA.fst, r = _sRA.snd - _sRB.snd; const ret = new Int32Array(l + r); let offset = 0; - for (let i = 0; i < l; i++) ret[offset++] = a.min + i; - for (let i = 1; i <= r; i++) ret[offset++] = b.max + i; + for (let i = 0; i < l; i++) ret[offset++] = _sRA.fst + i; + for (let i = 1; i <= r; i++) ret[offset++] = _sRB.snd + i; return OrderedSet.ofSortedArray(ret); } // non intersecting ranges are handled by top-level substract. - // at this point, b either contains a.min or a.max, but not both. - if (a.min < b.min) return OrderedSet.ofRange(a.min, b.min - 1); - return OrderedSet.ofRange(b.max + 1, a.max); + // at this point, b either contains rA.fst or rA.snd, but not both. + if (_sRA.fst < _sRB.fst) return OrderedSet.ofRange(_sRA.fst, _sRB.fst - 1); + return OrderedSet.ofRange(_sRB.snd + 1, _sRA.snd); } -function subtractAR(a: ArrayImpl, r: RangeImpl) { - if (!r.size) return a; +const _sAR = IntPair.zero(); +function subtractAR(a: Nums, r: number) { + IntPair.unpack(r, _sAR); + if (_sAR.snd < _sAR.fst) return a; - const xs = a.values; - const { min, max } = r; - const { start, end } = getStartEndA(xs, min, max); - const size = xs.length - (end - start); + const min = _sAR.fst, max = _sAR.snd; + const { start, end } = getStartEnd(a, min, max); + const size = a.length - (end - start); if (size <= 0) return OrderedSet.Empty; const ret = new Int32Array(size); let offset = 0; - for (let i = 0; i < start; i++) ret[offset++] = xs[i]; - for (let i = end, _i = xs.length; i < _i; i++) ret[offset++] = xs[i]; + for (let i = 0; i < start; i++) ret[offset++] = a[i]; + for (let i = end, _i = a.length; i < _i; i++) ret[offset++] = a[i]; return OrderedSet.ofSortedArray(ret); } -function subtractRA(r: RangeImpl, ys: ArrayLike<number>) { - if (!r.size) return r; +const _sAR1 = IntPair.zero(); +function subtractRA(r: number, b: Nums) { + IntPair.unpack(r, _sAR1); + if (_sAR1.snd < _sAR1.fst) return r; - const { min, max } = r; - const { start, end } = getStartEndA(ys, min, max); + const min = _sAR1.fst, max = _sAR1.snd; + const rSize = max - min + 1; + const { start, end } = getStartEnd(b, min, max); const commonCount = end - start; - const resultSize = r.size - commonCount; + const resultSize = rSize - commonCount; if (resultSize <= 0) return OrderedSet.Empty; const ret = new Int32Array(resultSize); - const li = ys.length - 1; - const fst = ys[Math.min(start, li)], last = ys[Math.min(end, li)]; + const li = b.length - 1; + const fst = b[Math.min(start, li)], last = b[Math.min(end, li)]; let offset = 0; for (let i = min; i < fst; i++) ret[offset++] = i; for (let i = fst; i <= last; i++) { - if (binarySearch(ys, i) < 0) ret[offset++] = i; + if (binarySearch(b, i) < 0) ret[offset++] = i; } for (let i = last + 1; i <= max; i++) ret[offset++] = i; return OrderedSet.ofSortedArray(ret); } -function subtractAA(a: ArrayImpl, b: ArrayImpl) { - const xs = a.values, ys = b.values; - const lenA = xs.length; +function subtractAA(a: Nums, b: Nums) { + const lenA = a.length; - let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(xs, ys); + let { i: sI, j: sJ, endA, endB } = getMaxIntersectionRange(a, b); let i = sI, j = sJ; let commonCount = 0; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { i++; } else if (x > y) { j++; } else { i++; j++; commonCount++; } @@ -463,19 +456,19 @@ function subtractAA(a: ArrayImpl, b: ArrayImpl) { let offset = 0; // insert the "prefix" - for (let k = 0; k < sI; k++) indices[offset++] = xs[k]; + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; i = sI; j = sJ; while (i <= endA && j <= endB) { - const x = xs[i], y = ys[j]; + const x = a[i], y = b[j]; if (x < y) { indices[offset++] = x; i++; } else if (x > y) { j++; } else { i++; j++; } } // insert the "tail" - for (; i < lenA; i++) indices[offset++] = xs[i]; + for (; i < lenA; i++) indices[offset++] = a[i]; return OrderedSet.ofSortedArray(indices); } diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index cc202559a750950725d6ccb3b0e30409029f6542..b9c084d8386d08fb4395e30ad47688d1f63c7d83 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -8,7 +8,6 @@ import Iterator from '../collections/iterator' import IntPair from '../collections/int-pair' import * as Sort from '../collections/sort' import OrderedSet from '../collections/ordered-set' -import OrderedSet1 from '../collections/ordered-set.1' import LinkedIndex from '../collections/linked-index' import MultiSet from '../collections/multi-set' @@ -129,13 +128,13 @@ describe('qsort-dual array', () => { test('shuffled', data, true); }) -function ordSetToArray(set: OrderedSet) { - const ret = []; - for (let i = 0; i < set.size; i++) ret.push(set.elementAt(i)); - return ret; -} - describe('ordered set', () => { + function ordSetToArray(set: OrderedSet) { + const ret = []; + for (let i = 0, _i = OrderedSet.size(set); i < _i; i++) ret.push(OrderedSet.elementAt(set, i)); + return ret; + } + function testEq(name: string, set: OrderedSet, expected: number[]) { it(name, () => { // copy the arrays to ensure "compatibility" between typed and native arrays @@ -189,23 +188,23 @@ describe('ordered set', () => { }); it('access/membership', () => { - expect(empty.has(10)).toBe(false); - expect(empty.indexOf(10)).toBe(-1); - - expect(singleton10.has(10)).toBe(true); - expect(singleton10.has(11)).toBe(false); - expect(singleton10.indexOf(10)).toBe(0); - expect(singleton10.indexOf(11)).toBe(-1); - - expect(range1_4.has(4)).toBe(true); - expect(range1_4.has(5)).toBe(false); - expect(range1_4.indexOf(4)).toBe(3); - expect(range1_4.indexOf(11)).toBe(-1); - - expect(arr136.has(3)).toBe(true); - expect(arr136.has(4)).toBe(false); - expect(arr136.indexOf(3)).toBe(1); - expect(arr136.indexOf(11)).toBe(-1); + expect(OrderedSet.has(empty, 10)).toBe(false); + expect(OrderedSet.indexOf(empty, 10)).toBe(-1); + + expect(OrderedSet.has(singleton10, 10)).toBe(true); + expect(OrderedSet.has(singleton10, 11)).toBe(false); + expect(OrderedSet.indexOf(singleton10, 10)).toBe(0); + expect(OrderedSet.indexOf(singleton10, 11)).toBe(-1); + + expect(OrderedSet.has(range1_4, 4)).toBe(true); + expect(OrderedSet.has(range1_4, 5)).toBe(false); + expect(OrderedSet.indexOf(range1_4, 4)).toBe(3); + expect(OrderedSet.indexOf(range1_4, 11)).toBe(-1); + + expect(OrderedSet.has(arr136, 3)).toBe(true); + expect(OrderedSet.has(arr136, 4)).toBe(false); + expect(OrderedSet.indexOf(arr136, 3)).toBe(1); + expect(OrderedSet.indexOf(arr136, 11)).toBe(-1); }); it('interval range', () => { @@ -273,150 +272,6 @@ describe('ordered set', () => { testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]); }); -describe('ordered set 1', () => { - function ordSet1ToArray(set: OrderedSet1) { - const ret = []; - for (let i = 0, _i = OrderedSet1.size(set); i < _i; i++) ret.push(OrderedSet1.elementAt(set, i)); - return ret; - } - - function testEq(name: string, set: OrderedSet1, expected: number[]) { - it(name, () => { - // copy the arrays to ensure "compatibility" between typed and native arrays - expect(Array.prototype.slice.call(ordSet1ToArray(set))).toEqual(Array.prototype.slice.call(expected)); - }); - } - - const empty = OrderedSet1.Empty; - const singleton10 = OrderedSet1.ofSingleton(10); - const range1_4 = OrderedSet1.ofRange(1, 4); - const arr136 = OrderedSet1.ofSortedArray([1, 3, 6]); - - testEq('empty', empty, []); - testEq('singleton', singleton10, [10]); - testEq('range', range1_4, [1, 2, 3, 4]); - testEq('sorted array', arr136, [1, 3, 6]); - - it('equality', () => { - expect(OrderedSet1.areEqual(empty, singleton10)).toBe(false); - expect(OrderedSet1.areEqual(singleton10, singleton10)).toBe(true); - expect(OrderedSet1.areEqual(range1_4, singleton10)).toBe(false); - expect(OrderedSet1.areEqual(arr136, OrderedSet1.ofSortedArray([1, 3, 6]))).toBe(true); - expect(OrderedSet1.areEqual(arr136, OrderedSet1.ofSortedArray([1, 4, 6]))).toBe(false); - }); - - it('areIntersecting', () => { - expect(OrderedSet1.areIntersecting(range1_4, arr136)).toBe(true); - expect(OrderedSet1.areIntersecting(empty, empty)).toBe(true); - expect(OrderedSet1.areIntersecting(empty, singleton10)).toBe(false); - expect(OrderedSet1.areIntersecting(empty, range1_4)).toBe(false); - expect(OrderedSet1.areIntersecting(empty, arr136)).toBe(false); - }); - - it('isSubset', () => { - expect(OrderedSet1.isSubset(singleton10, empty)).toBe(true); - expect(OrderedSet1.isSubset(range1_4, empty)).toBe(true); - expect(OrderedSet1.isSubset(arr136, empty)).toBe(true); - expect(OrderedSet1.isSubset(empty, empty)).toBe(true); - expect(OrderedSet1.isSubset(empty, singleton10)).toBe(false); - expect(OrderedSet1.isSubset(empty, range1_4)).toBe(false); - expect(OrderedSet1.isSubset(empty, arr136)).toBe(false); - - expect(OrderedSet1.isSubset(singleton10, range1_4)).toBe(false); - expect(OrderedSet1.isSubset(range1_4, OrderedSet1.ofRange(2, 3))).toBe(true); - expect(OrderedSet1.isSubset(arr136, range1_4)).toBe(false); - expect(OrderedSet1.isSubset(arr136, arr136)).toBe(true); - expect(OrderedSet1.isSubset(arr136, OrderedSet1.ofSortedArray([1, 3]))).toBe(true); - expect(OrderedSet1.isSubset(arr136, OrderedSet1.ofSortedArray([1, 3, 7]))).toBe(false); - expect(OrderedSet1.isSubset(OrderedSet1.ofSortedArray([0, 1, 2, 3, 7, 10]), OrderedSet1.ofSortedArray([1, 3, 7]))).toBe(true); - expect(OrderedSet1.isSubset(arr136, OrderedSet1.ofSortedArray([1, 3, 10, 45]))).toBe(false); - }); - - it('access/membership', () => { - expect(OrderedSet1.has(empty, 10)).toBe(false); - expect(OrderedSet1.indexOf(empty, 10)).toBe(-1); - - expect(OrderedSet1.has(singleton10, 10)).toBe(true); - expect(OrderedSet1.has(singleton10, 11)).toBe(false); - expect(OrderedSet1.indexOf(singleton10, 10)).toBe(0); - expect(OrderedSet1.indexOf(singleton10, 11)).toBe(-1); - - expect(OrderedSet1.has(range1_4, 4)).toBe(true); - expect(OrderedSet1.has(range1_4, 5)).toBe(false); - expect(OrderedSet1.indexOf(range1_4, 4)).toBe(3); - expect(OrderedSet1.indexOf(range1_4, 11)).toBe(-1); - - expect(OrderedSet1.has(arr136, 3)).toBe(true); - expect(OrderedSet1.has(arr136, 4)).toBe(false); - expect(OrderedSet1.indexOf(arr136, 3)).toBe(1); - expect(OrderedSet1.indexOf(arr136, 11)).toBe(-1); - }); - - it('interval range', () => { - expect(OrderedSet1.getIntervalRange(empty, 9, 11)).toEqual({ start: 0, end: 0 }); - expect(OrderedSet1.getIntervalRange(empty, -9, -6)).toEqual({ start: 0, end: 0 }); - expect(OrderedSet1.getIntervalRange(singleton10, 9, 11)).toEqual({ start: 0, end: 1 }); - expect(OrderedSet1.getIntervalRange(range1_4, 2, 3)).toEqual({ start: 1, end: 3 }); - expect(OrderedSet1.getIntervalRange(range1_4, -10, 2)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet1.getIntervalRange(range1_4, -10, 20)).toEqual({ start: 0, end: 4 }); - expect(OrderedSet1.getIntervalRange(range1_4, 3, 20)).toEqual({ start: 2, end: 4 }); - expect(OrderedSet1.getIntervalRange(arr136, 0, 1)).toEqual({ start: 0, end: 1 }); - expect(OrderedSet1.getIntervalRange(arr136, 0, 3)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet1.getIntervalRange(arr136, 0, 4)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet1.getIntervalRange(arr136, 2, 4)).toEqual({ start: 1, end: 2 }); - expect(OrderedSet1.getIntervalRange(arr136, 2, 7)).toEqual({ start: 1, end: 3 }); - }) - - testEq('union ES', OrderedSet1.union(empty, singleton10), [10]); - testEq('union ER', OrderedSet1.union(empty, range1_4), [1, 2, 3, 4]); - testEq('union EA', OrderedSet1.union(empty, arr136), [1, 3, 6]); - testEq('union SS', OrderedSet1.union(singleton10, OrderedSet1.ofSingleton(16)), [10, 16]); - testEq('union SR', OrderedSet1.union(range1_4, singleton10), [1, 2, 3, 4, 10]); - testEq('union SA', OrderedSet1.union(arr136, singleton10), [1, 3, 6, 10]); - testEq('union SA1', OrderedSet1.union(arr136, OrderedSet1.ofSingleton(3)), [1, 3, 6]); - testEq('union RR', OrderedSet1.union(range1_4, range1_4), [1, 2, 3, 4]); - testEq('union RR1', OrderedSet1.union(range1_4, OrderedSet1.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]); - testEq('union RR2', OrderedSet1.union(range1_4, OrderedSet1.ofRange(3, 5)), [1, 2, 3, 4, 5]); - testEq('union RA', OrderedSet1.union(range1_4, arr136), [1, 2, 3, 4, 6]); - testEq('union AA', OrderedSet1.union(arr136, OrderedSet1.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - testEq('union AA1', OrderedSet1.union(arr136, OrderedSet1.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - testEq('union AA2', OrderedSet1.union(arr136, OrderedSet1.ofSortedArray([2, 4, 5, 6, 7])), [1, 2, 3, 4, 5, 6, 7]); - - testEq('intersect ES', OrderedSet1.intersect(empty, singleton10), []); - testEq('intersect ER', OrderedSet1.intersect(empty, range1_4), []); - testEq('intersect EA', OrderedSet1.intersect(empty, arr136), []); - testEq('intersect SS', OrderedSet1.intersect(singleton10, OrderedSet1.ofSingleton(16)), []); - testEq('intersect SS1', OrderedSet1.intersect(singleton10, singleton10), [10]); - testEq('intersect SR', OrderedSet1.intersect(range1_4, singleton10), []); - testEq('intersect RR', OrderedSet1.intersect(range1_4, range1_4), [1, 2, 3, 4]); - testEq('intersect RR2', OrderedSet1.intersect(range1_4, OrderedSet1.ofRange(3, 5)), [3, 4]); - testEq('intersect RA', OrderedSet1.intersect(range1_4, arr136), [1, 3]); - testEq('intersect AA', OrderedSet1.intersect(arr136, OrderedSet1.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); - - testEq('subtract ES', OrderedSet1.subtract(empty, singleton10), []); - testEq('subtract ER', OrderedSet1.subtract(empty, range1_4), []); - testEq('subtract EA', OrderedSet1.subtract(empty, arr136), []); - testEq('subtract SS', OrderedSet1.subtract(singleton10, OrderedSet1.ofSingleton(16)), [10]); - testEq('subtract SS1', OrderedSet1.subtract(singleton10, singleton10), []); - testEq('subtract SR', OrderedSet1.subtract(range1_4, singleton10), [1, 2, 3, 4]); - testEq('subtract SR1', OrderedSet1.subtract(range1_4, OrderedSet1.ofSingleton(4)), [1, 2, 3]); - testEq('subtract SR2', OrderedSet1.subtract(range1_4, OrderedSet1.ofSingleton(3)), [1, 2, 4]); - testEq('subtract RR', OrderedSet1.subtract(range1_4, range1_4), []); - testEq('subtract RR1', OrderedSet1.subtract(range1_4, OrderedSet1.ofRange(3, 5)), [1, 2]); - - testEq('subtract RA', OrderedSet1.subtract(range1_4, arr136), [2, 4]); - testEq('subtract RA1', OrderedSet1.subtract(range1_4, OrderedSet1.ofSortedArray([0, 1, 2, 3, 4, 7])), []); - testEq('subtract RA2', OrderedSet1.subtract(range1_4, OrderedSet1.ofSortedArray([0, 2, 3])), [1, 4]); - - testEq('subtract AR', OrderedSet1.subtract(arr136, range1_4), [6]); - testEq('subtract AR1', OrderedSet1.subtract(arr136, OrderedSet1.ofRange(0, 10)), []); - testEq('subtract AR1', OrderedSet1.subtract(arr136, OrderedSet1.ofRange(2, 10)), [1]); - - testEq('subtract AA', OrderedSet1.subtract(arr136, arr136), []); - testEq('subtract AA1', OrderedSet1.subtract(arr136, OrderedSet1.ofSortedArray([2, 3, 4, 6, 7])), [1]); - testEq('subtract AA2', OrderedSet1.subtract(arr136, OrderedSet1.ofSortedArray([0, 1, 6])), [3]); -}); - describe('linked-index', () => { it('initial state', () => {