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);