Browse Source

collections

David Sehnal 7 years ago
parent
commit
a0ee98a70f

+ 0 - 1
src/structure/collections/group-set.ts

@@ -1 +0,0 @@
-// TODO

+ 323 - 0
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

+ 0 - 273
src/structure/collections/range-set.ts

@@ -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

+ 84 - 54
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', () => {