diff --git a/src/structure/collections/group-set.ts b/src/structure/collections/group-set.ts deleted file mode 100644 index 0ffdd02fcbce683e436c0030ffe0517135c6ceda..0000000000000000000000000000000000000000 --- a/src/structure/collections/group-set.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO \ No newline at end of file diff --git a/src/structure/collections/ordered-set.ts b/src/structure/collections/ordered-set.ts new file mode 100644 index 0000000000000000000000000000000000000000..67e3e349080ff46c376efcbca28409ff51ff435c --- /dev/null +++ b/src/structure/collections/ordered-set.ts @@ -0,0 +1,323 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Iterator from './iterator' + +/** An immutable ordered set. */ +interface OrderedSet { + readonly size: number, + has(x: number): boolean, + indexOf(x: number): number, + elementAt(i: number): number, + elements(): Iterator<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; } + toArray() { + const ret = new Array(this.size); + for (let i = 0; i < this.size; i++) ret[i] = i + this.min; + return ret; + } + 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; + } +} + +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]; } + elements() { return Iterator.Array(this.values); } + + constructor(public values: ArrayLike<number>) { + this.min = values[0]; + this.max = values[values.length - 1]; + this.size = values.length; + } +} + +namespace OrderedSet { + export function isEmpty(a: OrderedSet) { return a.size === 0; } + + export function hashCode(a: OrderedSet) { + // hash of tuple (size, min, max, mid) + const { size } = a; + let hash = 23; + if (!size) return hash; + hash = 31 * hash + size; + hash = 31 * hash + a.elementAt(0); + hash = 31 * hash + a.elementAt(size - 1); + if (size > 2) hash = 31 * hash + a.elementAt(size >> 1); + return hash; + } + // 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); + } + + 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); + } + + /** 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 union(a: OrderedSet, b: OrderedSet) { + 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).values, (b as ArrayImpl).values); + } + + export function intersect(a: OrderedSet, b: OrderedSet) { + 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); + } else { + if (!areRangesIntersecting(a, b)) return Empty; + return intersectAA((a as ArrayImpl).values, (b as ArrayImpl).values); + } + } + + 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); } + /** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ + export function ofSortedArray(xs: ArrayLike<number>): 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); + } + export const Empty = new RangeImpl(0, -1); +} + +function min(a: OrderedSet) { return (a as Impl).min; } +function max(a: OrderedSet) { return (a as Impl).max; } + +function binarySearch(xs: ArrayLike<number>, 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 equalAR(a: ArrayImpl, b: RangeImpl) { + return a.size === b.size && a.min === b.min && a.max === b.max; +} + +function equalAA(a: ArrayImpl, b: ArrayImpl) { + if (a.size !== b.size || a.min !== b.min || a.max !== b.max) return false; + const { size, values: xs } = a; + const { values: ys } = b; + for (let i = 0; i < size; i++) { + if (xs[i] !== ys[i]) return false; + } + return true; +} + +function areIntersectingAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { + const la = xs.length, lb = ys.length; + let i = 0, j = 0; + while (i < la && j < lb) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else return true; + } + return false; +} + +function isSubsetAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { + const la = xs.length, lb = ys.length; + let i = 0, j = 0, equal = 0; + while (i < la && j < lb) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; equal++; } + } + return equal === lb; +} + +function areRangesIntersecting(a: OrderedSet, b: OrderedSet) { + return a.size > 0 && b.size > 0 && max(a) >= min(b) && min(a) <= 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); +} + +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 unionAR(a: ArrayImpl, b: RangeImpl) { + if (!b.size) return a; + // is the array fully contained in the range? + if (a.min >= b.min && a.max <= b.max) return b; + + const xs = a.values; + const { min, max } = b; + + let start = 0, end = xs.length - 1; + while (xs[start] < min) { start++; } + while (xs[end] > max) { end--; } + end++; + + const size = start + (xs.length - end) + b.size; + const indices = new Int32Array(size); + let offset = 0; + for (let i = 0; i < start; i++) indices[offset++] = xs[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]; + + return OrderedSet.ofSortedArray(indices); +} + +function unionAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { + const la = xs.length, lb = ys.length; + + // sorted list merge. + + let i = 0, j = 0, resultSize = 0; + while (i < la && j < lb) { + const x = xs[i], y = ys[j]; + resultSize++; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; } + } + resultSize += Math.max(la - i, lb - j); + + const indices = new Int32Array(resultSize); + let offset = 0; + i = 0; + j = 0; + while (i < la && j < lb) { + const x = xs[i], y = ys[j]; + if (x < y) { indices[offset++] = x; i++; } + else if (x > y) { indices[offset++] = y; j++; } + else { indices[offset++] = x; i++; j++; } + } + for (; i < la; i++) { indices[offset++] = xs[i]; } + for (; j < lb; j++) { indices[offset++] = ys[j]; } + + return OrderedSet.ofSortedArray(indices); +} + +function intersectRR(a: RangeImpl, b: RangeImpl) { + if (!areRangesIntersecting(a, b)) return OrderedSet.Empty; + return OrderedSet.ofRange(Math.max(a.min, b.min), Math.min(a.max, b.max)); +} + +function intersectAR(a: ArrayImpl, r: RangeImpl) { + const xs = a.values; + let resultSize = 0; + for (let i = 0, _i = xs.length; i < _i; i++) { + if (r.has(xs[i])) resultSize++; + } + + if (!resultSize) return OrderedSet.Empty; + + const indices = new Int32Array(resultSize); + let offset = 0; + + for (let i = 0, _i = xs.length; i < _i; i++) { + if (r.has(xs[i])) indices[offset++] = xs[i]; + } + + return OrderedSet.ofSortedArray(indices); +} + +function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { + const la = xs.length, lb = ys.length; + + // a variation on sorted list merge. + + let i = 0, j = 0, resultSize = 0; + while (i < la && j < lb) { + const x = xs[i], y = ys[j]; + 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 = 0; + j = 0; + while (i < la && j < lb) { + 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); +} + +export default OrderedSet \ No newline at end of file diff --git a/src/structure/collections/range-set.ts b/src/structure/collections/range-set.ts deleted file mode 100644 index 8043407324b83c08bdb6fbf9060959e9aecd7015..0000000000000000000000000000000000000000 --- a/src/structure/collections/range-set.ts +++ /dev/null @@ -1,273 +0,0 @@ -/** - * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import Iterator from './iterator' - -interface RangeSet { - readonly size: number, - has(x: number): boolean, - indexOf(x: number): number, - elementAt(i: number): number, - elements(): Iterator<number> -} - -namespace RangeSet { - interface Impl extends RangeSet { - readonly min: number, - readonly max: number, - toArray(): ArrayLike<number> - } - - export function hashCode(a: RangeSet) { - // hash of tuple (size, min, max, mid) - const { size } = a; - let hash = 23; - if (!size) return hash; - hash = 31 * hash + size; - hash = 31 * hash + a.elementAt(0); - hash = 31 * hash + a.elementAt(size - 1); - if (size > 2) hash = 31 * hash + a.elementAt(size >> 1); - return hash; - } - // TODO: possibly add more hash functions to allow for multilevel hashing. - - export function areEqual(a: RangeSet, b: RangeSet) { - if (a === b) return true; - if (a instanceof RangeImpl) { - if (b instanceof RangeImpl) return a.min === b.min && a.max === b.max; - return equalAR(b as ArrayImpl, a); - } else if (b instanceof RangeImpl) { - return equalAR(a as ArrayImpl, b); - } - return equalAA(a as ArrayImpl, b as ArrayImpl); - } - - export function union(a: RangeSet, b: RangeSet) { - if (a instanceof RangeImpl) { - if (b instanceof RangeImpl) return unionRR(a, b); - return unionAR(b as ArrayImpl, a); - } else if (b instanceof RangeImpl) { - return unionAR(a as ArrayImpl, b); - } else return unionAA((a as Impl).toArray(), (b as Impl).toArray()); - } - - export function intersect(a: RangeSet, b: RangeSet) { - 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); - } else { - const ai = a as Impl, bi = b as Impl; - if (!areRangesIntersecting(ai, bi)) return Empty; - return intersectAA(ai.toArray(), bi.toArray()); - } - } - - 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; } - toArray() { - const ret = new Array(this.size); - for (let i = 0; i < this.size; i++) ret[i] = i + this.min; - return ret; - } - 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; - } - } - - 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; } - toArray() { return this.values; } - elementAt(i: number) { return this.values[i]; } - elements() { return Iterator.Array(this.values); } - - constructor(public values: ArrayLike<number>) { - this.min = values[0]; - this.max = values[values.length - 1]; - this.size = values.length; - } - } - - export function ofSingleton(value: number): RangeSet { return new RangeImpl(value, value); } - export function ofRange(min: number, max: number): RangeSet { return new RangeImpl(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>): RangeSet { - 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); - } - export const Empty = ofRange(0, -1); - - function binarySearch(xs: ArrayLike<number>, 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 equalAR(a: ArrayImpl, b: RangeImpl) { - return a.size === b.size && a.min === b.min && a.max === b.max; - } - - function equalAA(a: ArrayImpl, b: ArrayImpl) { - if (a.size !== b.size || a.min !== b.min || a.max !== b.max) return false; - const { size, values: xs } = a; - const { values: ys } = b; - for (let i = 0; i < size; i++) { - if (xs[i] !== ys[i]) return false; - } - return true; - } - - function areRangesIntersecting(a: Impl, b: Impl) { - return a.size > 0 && b.size > 0 && a.max >= b.min && a.min <= b.max; - } - - function unionRR(a: RangeImpl, b: RangeImpl) { - if (!a.size) return b; - if (!b.size) return a; - if (areRangesIntersecting(a, b)) return 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 ofSortedArray(arr); - } - - function unionAR(a: ArrayImpl, b: RangeImpl) { - if (!b.size) return a; - // is the array fully contained in the range? - if (a.min >= b.min && a.max <= b.max) return b; - - const xs = a.values; - const { min, max } = b; - - let start = 0, end = xs.length - 1; - while (xs[start] < min) { start++; } - while (xs[end] > max) { end--; } - end++; - - const size = start + (xs.length - end) + b.size; - const indices = new Int32Array(size); - let offset = 0; - for (let i = 0; i < start; i++) indices[offset++] = xs[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]; - - return ofSortedArray(indices); - } - - function unionAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { - const la = xs.length, lb = ys.length; - - // sorted list merge. - - let i = 0, j = 0, resultSize = 0; - while (i < la && j < lb) { - const x = xs[i], y = ys[j]; - resultSize++; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; } - } - resultSize += Math.max(la - i, lb - j); - - const indices = new Int32Array(resultSize); - let offset = 0; - i = 0; - j = 0; - while (i < la && j < lb) { - const x = xs[i], y = ys[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { indices[offset++] = y; j++; } - else { indices[offset++] = x; i++; j++; } - } - for (; i < la; i++) { indices[offset++] = xs[i]; } - for (; j < lb; j++) { indices[offset++] = ys[j]; } - - return ofSortedArray(indices); - } - - function intersectRR(a: RangeImpl, b: RangeImpl) { - if (!areRangesIntersecting(a, b)) return Empty; - return ofRange(Math.max(a.min, b.min), Math.min(a.max, b.max)); - } - - function intersectAR(a: ArrayImpl, r: RangeImpl) { - const xs = a.values; - let resultSize = 0; - for (let i = 0, _i = xs.length; i < _i; i++) { - if (r.has(xs[i])) resultSize++; - } - - if (!resultSize) return Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - - for (let i = 0, _i = xs.length; i < _i; i++) { - if (r.has(xs[i])) indices[offset++] = xs[i]; - } - - return ofSortedArray(indices); - } - - function intersectAA(xs: ArrayLike<number>, ys: ArrayLike<number>) { - const la = xs.length, lb = ys.length; - - // a variation on sorted list merge. - - let i = 0, j = 0, resultSize = 0; - while (i < la && j < lb) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; resultSize++; } - } - - if (!resultSize) return Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - i = 0; - j = 0; - while (i < la && j < lb) { - const x = xs[i], y = ys[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = x; i++; j++; } - } - - return ofSortedArray(indices); - } -} - -export default RangeSet \ No newline at end of file diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index 0b5276b3665731197d24d2ac6d4ea53264b16ce1..098c84be1a50bd0d4f31d0ce0a66e16d7807849a 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -7,7 +7,7 @@ import Iterator from '../collections/iterator' import IntPair from '../collections/int-pair' import * as Sort from '../collections/sort' -import RangeSet from '../collections/range-set' +import OrderedSet from '../collections/ordered-set' import LinkedIndex from '../collections/linked-index' describe('basic iterators', () => { @@ -121,71 +121,101 @@ describe('qsort-dual array', () => { }) describe('range set', () => { - function testEq(name: string, set: RangeSet, expected: number[]) { + 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(Iterator.toArray(set.elements()))).toEqual(Array.prototype.slice.call(expected)); }); } - const empty = RangeSet.Empty; - const singleton = RangeSet.ofSingleton(10); - const range = RangeSet.ofRange(1, 4); - const arr = RangeSet.ofSortedArray([1, 3, 6]); + const empty = OrderedSet.Empty; + const singleton = OrderedSet.ofSingleton(10); + const range = OrderedSet.ofRange(1, 4); + const arr = OrderedSet.ofSortedArray([1, 3, 6]); testEq('empty', empty, []); testEq('singleton', singleton, [10]); testEq('range', range, [1, 2, 3, 4]); testEq('sorted array', arr, [1, 3, 6]); - expect(RangeSet.areEqual(empty, singleton)).toBe(false); - expect(RangeSet.areEqual(singleton, singleton)).toBe(true); - expect(RangeSet.areEqual(range, singleton)).toBe(false); - expect(RangeSet.areEqual(arr, RangeSet.ofSortedArray([1, 3, 6]))).toBe(true); - expect(RangeSet.areEqual(arr, RangeSet.ofSortedArray([1, 4, 6]))).toBe(false); - - expect(empty.has(10)).toBe(false); - expect(empty.indexOf(10)).toBe(-1); - - expect(singleton.has(10)).toBe(true); - expect(singleton.has(11)).toBe(false); - expect(singleton.indexOf(10)).toBe(0); - expect(singleton.indexOf(11)).toBe(-1); - - expect(range.has(4)).toBe(true); - expect(range.has(5)).toBe(false); - expect(range.indexOf(4)).toBe(3); - expect(range.indexOf(11)).toBe(-1); - - expect(arr.has(3)).toBe(true); - expect(arr.has(4)).toBe(false); - expect(arr.indexOf(3)).toBe(1); - expect(arr.indexOf(11)).toBe(-1); - - testEq('union ES', RangeSet.union(empty, singleton), [10]); - testEq('union ER', RangeSet.union(empty, range), [1, 2, 3, 4]); - testEq('union EA', RangeSet.union(empty, arr), [1, 3, 6]); - testEq('union SS', RangeSet.union(singleton, RangeSet.ofSingleton(16)), [10, 16]); - testEq('union SR', RangeSet.union(range, singleton), [1, 2, 3, 4, 10]); - testEq('union SA', RangeSet.union(arr, singleton), [1, 3, 6, 10]); - testEq('union SA1', RangeSet.union(arr, RangeSet.ofSingleton(3)), [1, 3, 6]); - testEq('union RR', RangeSet.union(range, range), [1, 2, 3, 4]); - testEq('union RR1', RangeSet.union(range, RangeSet.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]); - testEq('union RR2', RangeSet.union(range, RangeSet.ofRange(3, 5)), [1, 2, 3, 4, 5]); - testEq('union RA', RangeSet.union(range, arr), [1, 2, 3, 4, 6]); - testEq('union AA', RangeSet.union(arr, RangeSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - testEq('union AA1', RangeSet.union(arr, RangeSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - - testEq('intersect ES', RangeSet.intersect(empty, singleton), []); - testEq('intersect ER', RangeSet.intersect(empty, range), []); - testEq('intersect EA', RangeSet.intersect(empty, arr), []); - testEq('intersect SS', RangeSet.intersect(singleton, RangeSet.ofSingleton(16)), []); - testEq('intersect SS', RangeSet.intersect(singleton, singleton), [10]); - testEq('intersect SR', RangeSet.intersect(range, singleton), []); - testEq('intersect RR', RangeSet.intersect(range, range), [1, 2, 3, 4]); - testEq('intersect RR2', RangeSet.intersect(range, RangeSet.ofRange(3, 5)), [3, 4]); - testEq('intersect RA', RangeSet.intersect(range, arr), [1, 3]); - testEq('intersect AA', RangeSet.intersect(arr, RangeSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); + it('equality', () => { + expect(OrderedSet.areEqual(empty, singleton)).toBe(false); + expect(OrderedSet.areEqual(singleton, singleton)).toBe(true); + expect(OrderedSet.areEqual(range, singleton)).toBe(false); + expect(OrderedSet.areEqual(arr, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(true); + expect(OrderedSet.areEqual(arr, OrderedSet.ofSortedArray([1, 4, 6]))).toBe(false); + }); + + it('areIntersecting', () => { + expect(OrderedSet.areIntersecting(range, arr)).toBe(true); + expect(OrderedSet.areIntersecting(empty, empty)).toBe(true); + expect(OrderedSet.areIntersecting(empty, singleton)).toBe(false); + expect(OrderedSet.areIntersecting(empty, range)).toBe(false); + expect(OrderedSet.areIntersecting(empty, arr)).toBe(false); + }); + + it('isSubset', () => { + expect(OrderedSet.isSubset(singleton, empty)).toBe(true); + expect(OrderedSet.isSubset(range, empty)).toBe(true); + expect(OrderedSet.isSubset(arr, empty)).toBe(true); + expect(OrderedSet.isSubset(empty, empty)).toBe(true); + expect(OrderedSet.isSubset(empty, singleton)).toBe(false); + expect(OrderedSet.isSubset(empty, range)).toBe(false); + expect(OrderedSet.isSubset(empty, arr)).toBe(false); + + expect(OrderedSet.isSubset(singleton, range)).toBe(false); + expect(OrderedSet.isSubset(range, OrderedSet.ofRange(2, 3))).toBe(true); + expect(OrderedSet.isSubset(arr, range)).toBe(false); + expect(OrderedSet.isSubset(arr, arr)).toBe(true); + expect(OrderedSet.isSubset(arr, OrderedSet.ofSortedArray([1, 3]))).toBe(true); + expect(OrderedSet.isSubset(arr, OrderedSet.ofSortedArray([1, 3, 7]))).toBe(false); + expect(OrderedSet.isSubset(arr, OrderedSet.ofSortedArray([1, 3, 10, 45]))).toBe(false); + }); + + it('access/membership', () => { + expect(empty.has(10)).toBe(false); + expect(empty.indexOf(10)).toBe(-1); + + expect(singleton.has(10)).toBe(true); + expect(singleton.has(11)).toBe(false); + expect(singleton.indexOf(10)).toBe(0); + expect(singleton.indexOf(11)).toBe(-1); + + expect(range.has(4)).toBe(true); + expect(range.has(5)).toBe(false); + expect(range.indexOf(4)).toBe(3); + expect(range.indexOf(11)).toBe(-1); + + expect(arr.has(3)).toBe(true); + expect(arr.has(4)).toBe(false); + expect(arr.indexOf(3)).toBe(1); + expect(arr.indexOf(11)).toBe(-1); + }); + + testEq('union ES', OrderedSet.union(empty, singleton), [10]); + testEq('union ER', OrderedSet.union(empty, range), [1, 2, 3, 4]); + testEq('union EA', OrderedSet.union(empty, arr), [1, 3, 6]); + testEq('union SS', OrderedSet.union(singleton, OrderedSet.ofSingleton(16)), [10, 16]); + testEq('union SR', OrderedSet.union(range, singleton), [1, 2, 3, 4, 10]); + testEq('union SA', OrderedSet.union(arr, singleton), [1, 3, 6, 10]); + testEq('union SA1', OrderedSet.union(arr, OrderedSet.ofSingleton(3)), [1, 3, 6]); + testEq('union RR', OrderedSet.union(range, range), [1, 2, 3, 4]); + testEq('union RR1', OrderedSet.union(range, OrderedSet.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]); + testEq('union RR2', OrderedSet.union(range, OrderedSet.ofRange(3, 5)), [1, 2, 3, 4, 5]); + testEq('union RA', OrderedSet.union(range, arr), [1, 2, 3, 4, 6]); + testEq('union AA', OrderedSet.union(arr, OrderedSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); + testEq('union AA1', OrderedSet.union(arr, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); + + testEq('intersect ES', OrderedSet.intersect(empty, singleton), []); + testEq('intersect ER', OrderedSet.intersect(empty, range), []); + testEq('intersect EA', OrderedSet.intersect(empty, arr), []); + testEq('intersect SS', OrderedSet.intersect(singleton, OrderedSet.ofSingleton(16)), []); + testEq('intersect SS', OrderedSet.intersect(singleton, singleton), [10]); + testEq('intersect SR', OrderedSet.intersect(range, singleton), []); + testEq('intersect RR', OrderedSet.intersect(range, range), [1, 2, 3, 4]); + testEq('intersect RR2', OrderedSet.intersect(range, OrderedSet.ofRange(3, 5)), [3, 4]); + testEq('intersect RA', OrderedSet.intersect(range, arr), [1, 3]); + testEq('intersect AA', OrderedSet.intersect(arr, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); }); describe('linked-index', () => {