diff --git a/src/perf-tests/ordered-set.ts b/src/perf-tests/ordered-set.ts new file mode 100644 index 0000000000000000000000000000000000000000..04b54aaa33d308c3f93a2a972c9e74720a21f5c9 --- /dev/null +++ b/src/perf-tests/ordered-set.ts @@ -0,0 +1,34 @@ +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))); + diff --git a/src/structure/collections/int-pair.ts b/src/structure/collections/int-pair.ts index e48e5cc763fb4e31b3f22acb617e95557e5f666b..939f358116a4bf304cba9aee9ac4b903377510e0 100644 --- a/src/structure/collections/int-pair.ts +++ b/src/structure/collections/int-pair.ts @@ -60,6 +60,12 @@ namespace IntPair { return _int32[1]; } + export function areEqual(a: number, b: number) { + _float64[0] = a; + _float64_1[0] = b; + 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; diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts index 9319c10fae19b1075655c42101d19b374c0acb8c..4b85f2e36d1e451f9dc30f4f413d7e99e6e59f57 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' +import OrderedSet from './ordered-set.1' import Iterator from './iterator' import IntPair from './int-pair' import { sortArray } from './sort' @@ -101,8 +101,11 @@ namespace MultiSet { class ElementsIterator implements Iterator<IntPair> { private pair = IntPair.zero(); private unit = 0; - private currentIndex = -1; - private currentIterator: Iterator<number> = Iterator.Empty; + + private setIndex = -1; + private currentIndex = 0; + private currentSize = 0; + private currentSet: OrderedSet = OrderedSet.Empty; [Symbol.iterator]() { return new ElementsIterator(this.elements); }; done: boolean; @@ -111,31 +114,32 @@ namespace MultiSet { move() { if (this.done) return this.pair; - let next = this.currentIterator.move(); - if (this.currentIterator.done) { - if (!this.advanceIterator()) { - this.done = true; - return this.pair; - } - next = this.currentIterator.move(); + if (this.currentIndex >= this.currentSize) { + if (!this.advance()) return this.pair; } + const next = OrderedSet.elementAt(this.currentSet, this.currentIndex++); this.pair.snd = next; return this.pair; } - private advanceIterator() { + private advance() { const keys = this.elements.keys; - if (++this.currentIndex >= keys.size) return false; - this.unit = keys.elementAt(this.currentIndex); + if (++this.setIndex >= keys.size) { + this.done = true; + return false; + } + this.unit = OrderedSet.elementAt(keys, this.setIndex); this.pair.fst = this.unit; - this.currentIterator = this.elements[this.unit].elements(); + this.currentSet = this.elements[this.unit]; + this.currentIndex = 0; + this.currentSize = this.currentSet.size; return true; } constructor(private elements: MultiSetElements) { this.done = elements.keys.size === 0; - this.advanceIterator(); + this.advance(); } } diff --git a/src/structure/collections/ordered-set.1.ts b/src/structure/collections/ordered-set.1.ts new file mode 100644 index 0000000000000000000000000000000000000000..cab641230f439cedf9832ac6bb02819630f07bb7 --- /dev/null +++ b/src/structure/collections/ordered-set.1.ts @@ -0,0 +1,476 @@ +/** + * 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 a63f210aca36f147921e3ef89f563282d700736a..2a24e2c869c0b30d7f3df48a9d54cb09fd5a49fb 100644 --- a/src/structure/collections/ordered-set.ts +++ b/src/structure/collections/ordered-set.ts @@ -4,7 +4,6 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Iterator from './iterator' import { hash3, hash4 } from './hash-functions' /** An immutable ordered set. */ @@ -12,8 +11,7 @@ interface OrderedSet { readonly size: number, has(x: number): boolean, indexOf(x: number): number, - elementAt(i: number): number, - elements(): Iterator<number> + elementAt(i: number): number } interface Impl extends OrderedSet { @@ -26,7 +24,6 @@ class RangeImpl implements Impl { 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; } - elements() { return Iterator.Range(this.min, this.max); } constructor(public min: number, public max: number) { this.size = max - min + 1; @@ -40,7 +37,6 @@ class ArrayImpl implements Impl { 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]; } - elements() { return Iterator.Array(this.values); } constructor(public values: ArrayLike<number>) { this.min = values[0]; diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index 1ad4858020498e97eb88446d1538e63133b50bf9..cc202559a750950725d6ccb3b0e30409029f6542 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -8,6 +8,7 @@ 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' @@ -128,11 +129,17 @@ describe('qsort-dual array', () => { test('shuffled', data, true); }) -describe('range set', () => { +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 testEq(name: string, set: OrderedSet, expected: number[]) { it(name, () => { // copy the arrays to ensure "compatibility" between typed and native arrays - expect(Array.prototype.slice.call(iteratorToArray(set.elements()))).toEqual(Array.prototype.slice.call(expected)); + expect(Array.prototype.slice.call(ordSetToArray(set))).toEqual(Array.prototype.slice.call(expected)); }); } @@ -266,6 +273,151 @@ describe('range 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', () => { const index = LinkedIndex(2);