From 40366b4d7bc11773f0272ce67c85c351221761a5 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Fri, 27 Oct 2017 23:22:48 +0200
Subject: [PATCH] segmentation & refactoring

---
 src/mol-base/_spec/collections.spec.ts        | 289 ----------
 .../collections/_spec/equiv-index.spec.ts     |  18 +
 .../collections/_spec/int-tuple.spec.ts       |  19 +
 .../collections/_spec/interval.spec.ts        |  73 +++
 .../collections/_spec/iterators.spec.ts       |  26 +
 .../collections/_spec/linked-index.spec.ts    |  50 ++
 .../_spec/ordered-set.spec.ts                 |  22 +-
 .../collections/_spec/segmentation.spec.ts    |  36 ++
 src/mol-base/collections/_spec/sort.spec.ts   |  90 ++++
 .../collections/_spec/sorted-array.spec.ts    |  50 ++
 .../segmentation.ts}                          |  35 +-
 src/mol-base/collections/ordered-set.ts       |   4 +-
 src/mol-base/collections/ordered-set/base.ts  | 501 ------------------
 src/mol-base/collections/segmentation.ts      |  26 +
 src/perf-tests/sets.ts                        |   8 +-
 15 files changed, 420 insertions(+), 827 deletions(-)
 delete mode 100644 src/mol-base/_spec/collections.spec.ts
 create mode 100644 src/mol-base/collections/_spec/equiv-index.spec.ts
 create mode 100644 src/mol-base/collections/_spec/int-tuple.spec.ts
 create mode 100644 src/mol-base/collections/_spec/interval.spec.ts
 create mode 100644 src/mol-base/collections/_spec/iterators.spec.ts
 create mode 100644 src/mol-base/collections/_spec/linked-index.spec.ts
 rename src/mol-base/{ => collections}/_spec/ordered-set.spec.ts (92%)
 create mode 100644 src/mol-base/collections/_spec/segmentation.spec.ts
 create mode 100644 src/mol-base/collections/_spec/sort.spec.ts
 create mode 100644 src/mol-base/collections/_spec/sorted-array.spec.ts
 rename src/mol-base/collections/{ordered-set/segment-iterator.ts => impl/segmentation.ts} (67%)
 delete mode 100644 src/mol-base/collections/ordered-set/base.ts
 create mode 100644 src/mol-base/collections/segmentation.ts

diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts
deleted file mode 100644
index 56a7c6009..000000000
--- a/src/mol-base/_spec/collections.spec.ts
+++ /dev/null
@@ -1,289 +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 '../collections/iterator'
-import IntTuple from '../collections/int-tuple'
-import * as Sort from '../collections/sort'
-import LinkedIndex from '../collections/linked-index'
-import EquivalenceClasses from '../collections/equivalence-classes'
-import Interval from '../collections/interval'
-import SortedArray from '../collections/sorted-array'
-
-function iteratorToArray<T>(it: Iterator<T>): T[] {
-    const ret = [];
-    for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = v;
-    return ret;
-}
-
-describe('basic iterators', () => {
-    function check<T>(name: string, iter: Iterator<T>, expected: T[]) {
-        it(name, () => {
-            expect(iteratorToArray(iter)).toEqual(expected);
-        });
-    }
-
-    check('empty', Iterator.Empty, []);
-    check('singleton', Iterator.Value(10), [10]);
-    check('array', Iterator.Array([1, 2, 3]), [1, 2, 3]);
-    check('range', Iterator.Range(0, 3), [0, 1, 2, 3]);
-});
-
-describe('int pair', () => {
-    it('works', () => {
-        for (let i = 0; i < 10; i++) {
-            for (let j = -10; j < 5; j++) {
-                const t = IntTuple.create(i, j);
-                expect(IntTuple.fst(t)).toBe(i);
-                expect(IntTuple.snd(t)).toBe(j);
-            }
-        }
-    })
-})
-
-function shuffle<T>(data: T, len: number, clone: (s: T) => T, swap: Sort.Swapper = Sort.arraySwap) {
-    const a = clone(data);
-    for (let i = len - 1; i > 0; i--) {
-        const j = Math.floor(Math.random() * (i + 1));
-        swap(a, i, j);
-    }
-    return a;
-}
-
-function shuffleArray(data: any[]) {
-    return shuffle(data, data.length, t => [...t]);
-}
-
-describe('qsort-array asc', () => {
-    const data0 = new Array(50);
-    for (let i = 0; i < data0.length; i++) data0[i] = i;
-    const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6];
-
-    function test(name: string, data: any[], randomize: boolean) {
-        it(name, () => {
-            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
-            if (randomize) {
-                for (let i = 0; i < 10; i++) {
-                    expect(Sort.sortArray(shuffleArray(data))).toEqual(data);
-                }
-            } else {
-                expect(Sort.sortArray([...data])).toEqual(data);
-            }
-        });
-    }
-    test('uniq', data0, false);
-    test('uniq shuffle', data0, true);
-    test('rep', data1, false);
-    test('rep shuffle', data1, true);
-})
-
-describe('qsort-array generic', () => {
-    const data0 = new Array(50);
-    for (let i = 0; i < data0.length; i++) data0[i] = i;
-    const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6];
-
-    function test(name: string, data: any[], randomize: boolean) {
-        it(name, () => {
-            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
-            if (randomize) {
-                for (let i = 0; i < 10; i++) {
-                    expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
-                }
-            } else {
-                expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
-            }
-        });
-    }
-    test('uniq', data0, false);
-    test('uniq shuffle', data0, true);
-    test('rep', data1, false);
-    test('rep shuffle', data1, true);
-})
-
-describe('qsort-dual array', () => {
-    const len = 3;
-    const data = { xs: [0, 1, 2], ys: ['x', 'y', 'z'] };
-
-    const cmp: Sort.Comparer<typeof data> = (data, i, j) => data.xs[i] - data.xs[j];
-    const swap: Sort.Swapper<typeof data> = (data, i, j) => { Sort.arraySwap(data.xs, i, j); Sort.arraySwap(data.ys, i, j); }
-    const clone = (d: typeof data) => ({ xs: [...d.xs], ys: [...d.ys] })
-
-    function test(name: string, src: typeof data, randomize: boolean) {
-        it(name, () => {
-            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
-            if (randomize) {
-                for (let i = 0; i < 10; i++) {
-                    expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data);
-                }
-            } else {
-                expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data);
-            }
-        });
-    }
-    test('sorted', data, false);
-    test('shuffled', data, true);
-})
-
-describe('interval', () => {
-    function testI(name: string, a: Interval, b: Interval) {
-        it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
-    }
-
-    function test(name: string, a: any, b: any) {
-        it(name, () => expect(a).toEqual(b));
-    }
-
-    const e = Interval.Empty;
-    const r05 = Interval.ofRange(0, 5);
-    const se05 = Interval.ofBounds(0, 5);
-
-    test('size', Interval.size(e), 0);
-    test('size', Interval.size(r05), 6);
-    test('size', Interval.size(se05), 5);
-
-    test('min/max', [Interval.min(e), Interval.max(e)], [0, -1]);
-    test('min/max', [Interval.min(r05), Interval.max(r05)], [0, 5]);
-    test('min/max', [Interval.min(se05), Interval.max(se05)], [0, 4]);
-
-    test('start/end', [Interval.start(e), Interval.end(e)], [0, 0]);
-    test('start/end', [Interval.start(r05), Interval.end(r05)], [0, 6]);
-    test('start/end', [Interval.start(se05), Interval.end(se05)], [0, 5]);
-
-    test('has', Interval.has(e, 5), false);
-    test('has', Interval.has(r05, 5), true);
-    test('has', Interval.has(r05, 6), false);
-    test('has', Interval.has(r05, -1), false);
-    test('has', Interval.has(se05, 5), false);
-    test('has', Interval.has(se05, 4), true);
-
-    test('indexOf', Interval.indexOf(e, 5), -1);
-    test('indexOf', Interval.indexOf(r05, 5), 5);
-    test('indexOf', Interval.indexOf(r05, 6), -1);
-
-    test('getAt', Interval.getAt(r05, 5), 5);
-
-    test('areEqual', Interval.areEqual(r05, se05), false);
-    test('areIntersecting1', Interval.areIntersecting(r05, se05), true);
-    test('areIntersecting2', Interval.areIntersecting(r05, e), false);
-    test('areIntersecting3', Interval.areIntersecting(e, r05), false);
-    test('areIntersecting4', Interval.areIntersecting(e, e), true);
-
-    test('areIntersecting5', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), true);
-    test('areIntersecting6', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), false);
-    test('areIntersecting7', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), true);
-    test('areIntersecting8', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), true);
-
-    test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), false);
-    test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), true);
-
-    testI('intersect', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), Interval.ofRange(0, 3));
-    testI('intersect1', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(1, 3)), Interval.ofRange(1, 3));
-    testI('intersect2', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), Interval.ofRange(3, 5));
-    testI('intersect3', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), Interval.Empty);
-
-    test('predIndex1', Interval.findPredecessorIndex(r05, 5), 5);
-    test('predIndex2', Interval.findPredecessorIndex(r05, -1), 0);
-    test('predIndex3', Interval.findPredecessorIndex(r05, 6), 6);
-    test('predIndexInt', Interval.findPredecessorIndexInInterval(r05, 0, Interval.ofRange(2, 3)), 2);
-    test('predIndexInt1', Interval.findPredecessorIndexInInterval(r05, 4, Interval.ofRange(2, 3)), 4);
-
-    testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3));
-});
-
-describe('sortedArray', () => {
-    function testI(name: string, a: Interval, b: Interval) {
-        it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
-    }
-
-    function test(name: string, a: any, b: any) {
-        it(name, () => expect(a).toEqual(b));
-    }
-
-    const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
-    const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
-
-    test('size', SortedArray.size(a1234), 4);
-
-    test('min/max', [SortedArray.min(a1234), SortedArray.max(a1234)], [1, 4]);
-    test('start/end', [SortedArray.start(a1234), SortedArray.end(a1234)], [1, 5]);
-
-    test('has', SortedArray.has(a1234, 5), false);
-    test('has', SortedArray.has(a1234, 4), true);
-
-    it('has-all', () => {
-        for (let i = 1; i <= 4; i++) expect(SortedArray.has(a1234, i)).toBe(true);
-    });
-
-    test('indexOf', SortedArray.indexOf(a2468, 5), -1);
-    test('indexOf', SortedArray.indexOf(a2468, 2), 0);
-
-    test('getAt', SortedArray.getAt(a2468, 1), 4);
-
-    test('areEqual', SortedArray.areEqual(a2468, a2468), true);
-    test('areEqual1', SortedArray.areEqual(a2468, SortedArray.create([4, 2, 8, 6])), true);
-    test('areEqual2', SortedArray.areEqual(a1234, a2468), false);
-
-    test('predIndex1', SortedArray.findPredecessorIndex(a1234, 5), 4);
-    test('predIndex2', SortedArray.findPredecessorIndex(a1234, 2), 1);
-    test('predIndex3', SortedArray.findPredecessorIndex(a2468, 4), 1);
-    test('predIndex4', SortedArray.findPredecessorIndex(a2468, 3), 1);
-    test('predIndexInt', SortedArray.findPredecessorIndexInInterval(a1234, 0, Interval.ofRange(2, 3)), 2);
-
-    testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1));
-});
-
-describe('linked-index', () => {
-    it('initial state', () => {
-        const index = LinkedIndex(2);
-        expect(index.head).toBe(0);
-        expect(index.has(0)).toBe(true);
-        expect(index.has(1)).toBe(true);
-    });
-
-    it('singleton', () => {
-        const index = LinkedIndex(1);
-        expect(index.head).toBe(0);
-        expect(index.has(0)).toBe(true);
-        index.remove(0);
-        expect(index.head).toBe(-1);
-        expect(index.has(0)).toBe(false);
-    });
-
-    it('remove 0', () => {
-        const index = LinkedIndex(2);
-        index.remove(0);
-        expect(index.head).toBe(1);
-        expect(index.has(0)).toBe(false);
-        expect(index.has(1)).toBe(true);
-    });
-
-    it('remove 1', () => {
-        const index = LinkedIndex(2);
-        index.remove(1);
-        expect(index.head).toBe(0);
-        expect(index.has(0)).toBe(true);
-        expect(index.has(1)).toBe(false);
-    });
-
-    it('remove 01', () => {
-        const index = LinkedIndex(2);
-        index.remove(0);
-        index.remove(1);
-        expect(index.head).toBe(-1);
-        expect(index.has(0)).toBe(false);
-        expect(index.has(1)).toBe(false);
-    });
-});
-
-describe('equiv-classes', () => {
-    it('integer mod classes', () => {
-        const cls = EquivalenceClasses<number, number>(x => x % 2, (a, b) => (a - b) % 2 === 0);
-        for (let i = 0; i < 6; i++) cls.add(i, i);
-
-        expect(cls.groups.length).toBe(2);
-        expect(cls.groups[0]).toEqual([0, 2, 4]);
-        expect(cls.groups[1]).toEqual([1, 3, 5]);
-    });
-});
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/equiv-index.spec.ts b/src/mol-base/collections/_spec/equiv-index.spec.ts
new file mode 100644
index 000000000..967bea3b0
--- /dev/null
+++ b/src/mol-base/collections/_spec/equiv-index.spec.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import EquivalenceClasses from '../equivalence-classes'
+
+describe('equiv-classes', () => {
+    it('integer mod classes', () => {
+        const cls = EquivalenceClasses<number, number>(x => x % 2, (a, b) => (a - b) % 2 === 0);
+        for (let i = 0; i < 6; i++) cls.add(i, i);
+
+        expect(cls.groups.length).toBe(2);
+        expect(cls.groups[0]).toEqual([0, 2, 4]);
+        expect(cls.groups[1]).toEqual([1, 3, 5]);
+    });
+});
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/int-tuple.spec.ts b/src/mol-base/collections/_spec/int-tuple.spec.ts
new file mode 100644
index 000000000..acdfb01b6
--- /dev/null
+++ b/src/mol-base/collections/_spec/int-tuple.spec.ts
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import IntTuple from '../int-tuple'
+
+describe('int pair', () => {
+    it('works', () => {
+        for (let i = 0; i < 10; i++) {
+            for (let j = -10; j < 5; j++) {
+                const t = IntTuple.create(i, j);
+                expect(IntTuple.fst(t)).toBe(i);
+                expect(IntTuple.snd(t)).toBe(j);
+            }
+        }
+    })
+})
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/interval.spec.ts b/src/mol-base/collections/_spec/interval.spec.ts
new file mode 100644
index 000000000..cda22df8e
--- /dev/null
+++ b/src/mol-base/collections/_spec/interval.spec.ts
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Interval from '../interval'
+
+describe('interval', () => {
+    function testI(name: string, a: Interval, b: Interval) {
+        it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
+    }
+
+    function test(name: string, a: any, b: any) {
+        it(name, () => expect(a).toEqual(b));
+    }
+
+    const e = Interval.Empty;
+    const r05 = Interval.ofRange(0, 5);
+    const se05 = Interval.ofBounds(0, 5);
+
+    test('size', Interval.size(e), 0);
+    test('size', Interval.size(r05), 6);
+    test('size', Interval.size(se05), 5);
+
+    test('min/max', [Interval.min(e), Interval.max(e)], [0, -1]);
+    test('min/max', [Interval.min(r05), Interval.max(r05)], [0, 5]);
+    test('min/max', [Interval.min(se05), Interval.max(se05)], [0, 4]);
+
+    test('start/end', [Interval.start(e), Interval.end(e)], [0, 0]);
+    test('start/end', [Interval.start(r05), Interval.end(r05)], [0, 6]);
+    test('start/end', [Interval.start(se05), Interval.end(se05)], [0, 5]);
+
+    test('has', Interval.has(e, 5), false);
+    test('has', Interval.has(r05, 5), true);
+    test('has', Interval.has(r05, 6), false);
+    test('has', Interval.has(r05, -1), false);
+    test('has', Interval.has(se05, 5), false);
+    test('has', Interval.has(se05, 4), true);
+
+    test('indexOf', Interval.indexOf(e, 5), -1);
+    test('indexOf', Interval.indexOf(r05, 5), 5);
+    test('indexOf', Interval.indexOf(r05, 6), -1);
+
+    test('getAt', Interval.getAt(r05, 5), 5);
+
+    test('areEqual', Interval.areEqual(r05, se05), false);
+    test('areIntersecting1', Interval.areIntersecting(r05, se05), true);
+    test('areIntersecting2', Interval.areIntersecting(r05, e), false);
+    test('areIntersecting3', Interval.areIntersecting(e, r05), false);
+    test('areIntersecting4', Interval.areIntersecting(e, e), true);
+
+    test('areIntersecting5', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), true);
+    test('areIntersecting6', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), false);
+    test('areIntersecting7', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), true);
+    test('areIntersecting8', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), true);
+
+    test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), false);
+    test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), true);
+
+    testI('intersect', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), Interval.ofRange(0, 3));
+    testI('intersect1', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(1, 3)), Interval.ofRange(1, 3));
+    testI('intersect2', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), Interval.ofRange(3, 5));
+    testI('intersect3', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), Interval.Empty);
+
+    test('predIndex1', Interval.findPredecessorIndex(r05, 5), 5);
+    test('predIndex2', Interval.findPredecessorIndex(r05, -1), 0);
+    test('predIndex3', Interval.findPredecessorIndex(r05, 6), 6);
+    test('predIndexInt', Interval.findPredecessorIndexInInterval(r05, 0, Interval.ofRange(2, 3)), 2);
+    test('predIndexInt1', Interval.findPredecessorIndexInInterval(r05, 4, Interval.ofRange(2, 3)), 4);
+
+    testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3));
+});
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/iterators.spec.ts b/src/mol-base/collections/_spec/iterators.spec.ts
new file mode 100644
index 000000000..fba704b58
--- /dev/null
+++ b/src/mol-base/collections/_spec/iterators.spec.ts
@@ -0,0 +1,26 @@
+/**
+ * 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'
+
+function iteratorToArray<T>(it: Iterator<T>): T[] {
+    const ret = [];
+    for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = v;
+    return ret;
+}
+
+describe('basic iterators', () => {
+    function check<T>(name: string, iter: Iterator<T>, expected: T[]) {
+        it(name, () => {
+            expect(iteratorToArray(iter)).toEqual(expected);
+        });
+    }
+
+    check('empty', Iterator.Empty, []);
+    check('singleton', Iterator.Value(10), [10]);
+    check('array', Iterator.Array([1, 2, 3]), [1, 2, 3]);
+    check('range', Iterator.Range(0, 3), [0, 1, 2, 3]);
+});
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/linked-index.spec.ts b/src/mol-base/collections/_spec/linked-index.spec.ts
new file mode 100644
index 000000000..9f811f2e9
--- /dev/null
+++ b/src/mol-base/collections/_spec/linked-index.spec.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import LinkedIndex from '../linked-index'
+
+describe('linked-index', () => {
+    it('initial state', () => {
+        const index = LinkedIndex(2);
+        expect(index.head).toBe(0);
+        expect(index.has(0)).toBe(true);
+        expect(index.has(1)).toBe(true);
+    });
+
+    it('singleton', () => {
+        const index = LinkedIndex(1);
+        expect(index.head).toBe(0);
+        expect(index.has(0)).toBe(true);
+        index.remove(0);
+        expect(index.head).toBe(-1);
+        expect(index.has(0)).toBe(false);
+    });
+
+    it('remove 0', () => {
+        const index = LinkedIndex(2);
+        index.remove(0);
+        expect(index.head).toBe(1);
+        expect(index.has(0)).toBe(false);
+        expect(index.has(1)).toBe(true);
+    });
+
+    it('remove 1', () => {
+        const index = LinkedIndex(2);
+        index.remove(1);
+        expect(index.head).toBe(0);
+        expect(index.has(0)).toBe(true);
+        expect(index.has(1)).toBe(false);
+    });
+
+    it('remove 01', () => {
+        const index = LinkedIndex(2);
+        index.remove(0);
+        index.remove(1);
+        expect(index.head).toBe(-1);
+        expect(index.has(0)).toBe(false);
+        expect(index.has(1)).toBe(false);
+    });
+});
\ No newline at end of file
diff --git a/src/mol-base/_spec/ordered-set.spec.ts b/src/mol-base/collections/_spec/ordered-set.spec.ts
similarity index 92%
rename from src/mol-base/_spec/ordered-set.spec.ts
rename to src/mol-base/collections/_spec/ordered-set.spec.ts
index 11ff74e71..41b70f708 100644
--- a/src/mol-base/_spec/ordered-set.spec.ts
+++ b/src/mol-base/collections/_spec/ordered-set.spec.ts
@@ -4,8 +4,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import OrderedSet from '../collections/ordered-set'
-import Interval from '../collections/interval'
+import OrderedSet from '../ordered-set'
+import Interval from '../interval'
 
 describe('ordered set', () => {
     function ordSetToArray(set: OrderedSet) {
@@ -157,22 +157,4 @@ describe('ordered set', () => {
     testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []);
     testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]);
     testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]);
-
-    it('segments', () => {
-        const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);
-        const segs = OrderedSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]);
-        const it = OrderedSet.segments(segs, data);
-
-        const t = Object.create(null);
-        for (let s = it.move(); !it.done; s = it.move()) {
-            for (let j = s.start; j < s.end; j++) {
-                const x = t[s.segment];
-                const v = OrderedSet.getAt(data, j);
-                if (!x) t[s.segment] = [v];
-                else x[x.length] = v;
-            }
-        }
-
-        expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] });
-    });
 });
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/segmentation.spec.ts b/src/mol-base/collections/_spec/segmentation.spec.ts
new file mode 100644
index 000000000..8cd02e248
--- /dev/null
+++ b/src/mol-base/collections/_spec/segmentation.spec.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import OrderedSet from '../ordered-set'
+import Interval from '../interval'
+import Segmentation from '../segmentation'
+import SortedArray from '../sorted-array'
+
+describe('segments', () => {
+    const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);
+    const segs = Segmentation.create(SortedArray.ofSortedArray([0, 4, 10, 12, 13, 15, 25]), [])
+
+    it('project', () => {
+        const p = Segmentation.projectValue(segs, data, 4);
+        expect(p).toBe(Interval.ofBounds(0, 2))
+    });
+
+    it('iteration', () => {
+        const it = Segmentation.segments(segs, data);
+
+        const t = Object.create(null);
+        for (let s = it.move(); !it.done; s = it.move()) {
+            for (let j = s.start; j < s.end; j++) {
+                const x = t[s.index];
+                const v = OrderedSet.getAt(data, j);
+                if (!x) t[s.index] = [v];
+                else x[x.length] = v;
+            }
+        }
+
+        expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] });
+    });
+});
diff --git a/src/mol-base/collections/_spec/sort.spec.ts b/src/mol-base/collections/_spec/sort.spec.ts
new file mode 100644
index 000000000..4b6773b76
--- /dev/null
+++ b/src/mol-base/collections/_spec/sort.spec.ts
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as Sort from '../sort'
+
+function shuffle<T>(data: T, len: number, clone: (s: T) => T, swap: Sort.Swapper = Sort.arraySwap) {
+    const a = clone(data);
+    for (let i = len - 1; i > 0; i--) {
+        const j = Math.floor(Math.random() * (i + 1));
+        swap(a, i, j);
+    }
+    return a;
+}
+
+function shuffleArray(data: any[]) {
+    return shuffle(data, data.length, t => [...t]);
+}
+
+describe('qsort-array asc', () => {
+    const data0 = new Array(50);
+    for (let i = 0; i < data0.length; i++) data0[i] = i;
+    const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6];
+
+    function test(name: string, data: any[], randomize: boolean) {
+        it(name, () => {
+            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
+            if (randomize) {
+                for (let i = 0; i < 10; i++) {
+                    expect(Sort.sortArray(shuffleArray(data))).toEqual(data);
+                }
+            } else {
+                expect(Sort.sortArray([...data])).toEqual(data);
+            }
+        });
+    }
+    test('uniq', data0, false);
+    test('uniq shuffle', data0, true);
+    test('rep', data1, false);
+    test('rep shuffle', data1, true);
+})
+
+describe('qsort-array generic', () => {
+    const data0 = new Array(50);
+    for (let i = 0; i < data0.length; i++) data0[i] = i;
+    const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6];
+
+    function test(name: string, data: any[], randomize: boolean) {
+        it(name, () => {
+            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
+            if (randomize) {
+                for (let i = 0; i < 10; i++) {
+                    expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
+                }
+            } else {
+                expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
+            }
+        });
+    }
+    test('uniq', data0, false);
+    test('uniq shuffle', data0, true);
+    test('rep', data1, false);
+    test('rep shuffle', data1, true);
+})
+
+describe('qsort-dual array', () => {
+    const len = 3;
+    const data = { xs: [0, 1, 2], ys: ['x', 'y', 'z'] };
+
+    const cmp: Sort.Comparer<typeof data> = (data, i, j) => data.xs[i] - data.xs[j];
+    const swap: Sort.Swapper<typeof data> = (data, i, j) => { Sort.arraySwap(data.xs, i, j); Sort.arraySwap(data.ys, i, j); }
+    const clone = (d: typeof data) => ({ xs: [...d.xs], ys: [...d.ys] })
+
+    function test(name: string, src: typeof data, randomize: boolean) {
+        it(name, () => {
+            // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
+            if (randomize) {
+                for (let i = 0; i < 10; i++) {
+                    expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data);
+                }
+            } else {
+                expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data);
+            }
+        });
+    }
+    test('sorted', data, false);
+    test('shuffled', data, true);
+})
\ No newline at end of file
diff --git a/src/mol-base/collections/_spec/sorted-array.spec.ts b/src/mol-base/collections/_spec/sorted-array.spec.ts
new file mode 100644
index 000000000..d0e3ab9e6
--- /dev/null
+++ b/src/mol-base/collections/_spec/sorted-array.spec.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Interval from '../interval'
+import SortedArray from '../sorted-array'
+
+describe('sortedArray', () => {
+    function testI(name: string, a: Interval, b: Interval) {
+        it(name, () => expect(Interval.areEqual(a, b)).toBe(true));
+    }
+
+    function test(name: string, a: any, b: any) {
+        it(name, () => expect(a).toEqual(b));
+    }
+
+    const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
+    const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
+
+    test('size', SortedArray.size(a1234), 4);
+
+    test('min/max', [SortedArray.min(a1234), SortedArray.max(a1234)], [1, 4]);
+    test('start/end', [SortedArray.start(a1234), SortedArray.end(a1234)], [1, 5]);
+
+    test('has', SortedArray.has(a1234, 5), false);
+    test('has', SortedArray.has(a1234, 4), true);
+
+    it('has-all', () => {
+        for (let i = 1; i <= 4; i++) expect(SortedArray.has(a1234, i)).toBe(true);
+    });
+
+    test('indexOf', SortedArray.indexOf(a2468, 5), -1);
+    test('indexOf', SortedArray.indexOf(a2468, 2), 0);
+
+    test('getAt', SortedArray.getAt(a2468, 1), 4);
+
+    test('areEqual', SortedArray.areEqual(a2468, a2468), true);
+    test('areEqual1', SortedArray.areEqual(a2468, SortedArray.create([4, 2, 8, 6])), true);
+    test('areEqual2', SortedArray.areEqual(a1234, a2468), false);
+
+    test('predIndex1', SortedArray.findPredecessorIndex(a1234, 5), 4);
+    test('predIndex2', SortedArray.findPredecessorIndex(a1234, 2), 1);
+    test('predIndex3', SortedArray.findPredecessorIndex(a2468, 4), 1);
+    test('predIndex4', SortedArray.findPredecessorIndex(a2468, 3), 1);
+    test('predIndexInt', SortedArray.findPredecessorIndexInInterval(a1234, 0, Interval.ofRange(2, 3)), 2);
+
+    testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1));
+});
\ No newline at end of file
diff --git a/src/mol-base/collections/ordered-set/segment-iterator.ts b/src/mol-base/collections/impl/segmentation.ts
similarity index 67%
rename from src/mol-base/collections/ordered-set/segment-iterator.ts
rename to src/mol-base/collections/impl/segmentation.ts
index 634f7d245..1fd21916e 100644
--- a/src/mol-base/collections/ordered-set/segment-iterator.ts
+++ b/src/mol-base/collections/impl/segmentation.ts
@@ -7,13 +7,30 @@
 import Iterator from '../iterator'
 import OrderedSet from '../ordered-set'
 import Interval from '../interval'
+import SortedArray from '../sorted-array'
+import Segs from '../segmentation'
 
-class SegmentIterator implements Iterator<{ segment: number, start: number, end: number }> {
+type Segmentation = { segments: OrderedSet, segmentIndex: ArrayLike<number> }
+
+export function create(segments: SortedArray, segmentIndex: ArrayLike<number>): Segmentation {
+    return { segments, segmentIndex };
+}
+
+export function getSegment({ segmentIndex }: Segmentation, value: number) {
+    return segmentIndex[value];
+}
+
+export function projectValue({ segments }: Segmentation, set: OrderedSet, value: number): Interval {
+    const last = OrderedSet.max(segments);
+    const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(segments, value - 1);
+    return OrderedSet.findRange(set, OrderedSet.getAt(segments, idx), OrderedSet.getAt(segments, idx + 1) - 1);
+}
+
+class SegmentIterator implements Iterator<Segs.Segment> {
     private segmentStart = 0;
     private segmentEnd = 0;
-    // private segmentRange = Interval.Empty;
     private setRange = Interval.Empty;
-    private value = { segment: 0, start: 0, end: 0 };
+    private value: Segs.Segment = { index: 0, start: 0, end: 0 };
     private last: number = 0;
 
     [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set, this.inputRange); };
@@ -30,7 +47,7 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end:
             if (!this.updateValue()) {
                 this.updateSegmentRange();
             } else {
-                this.value.segment = this.segmentStart++;
+                this.value.index = this.segmentStart++;
                 break;
             }
         }
@@ -48,8 +65,6 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end:
         this.value.start = Interval.start(this.setRange);
         this.value.end = setEnd;
         this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange))
-        //this.setRange.start = setEnd;
-        //throw '';
         return setEnd > this.value.start;
     }
 
@@ -68,9 +83,7 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end:
     }
 }
 
-function createIterator(segments: OrderedSet, set: OrderedSet, range?: Interval) {
+export function segments(segs: Segmentation, set: OrderedSet, range?: Interval) {
     const int = typeof range !== 'undefined' ? range : Interval.ofBounds(0, OrderedSet.size(set));
-    return new SegmentIterator(segments, set, int);
-}
-
-export default createIterator
\ No newline at end of file
+    return new SegmentIterator(segs.segments, set, int);
+}
\ No newline at end of file
diff --git a/src/mol-base/collections/ordered-set.ts b/src/mol-base/collections/ordered-set.ts
index 37b980658..1be957f36 100644
--- a/src/mol-base/collections/ordered-set.ts
+++ b/src/mol-base/collections/ordered-set.ts
@@ -36,10 +36,8 @@ namespace OrderedSet {
     export const findPredecessorIndex: (set: OrderedSet, x: number) => number = Base.findPredecessorIndex as any;
     export const findPredecessorIndexInRange: (set: OrderedSet, x: number, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
     export const findRange: (set: OrderedSet, min: number, max: number) => Interval = Base.findRange as any;
-
-    export const segments = SegmentIterator;
 }
 
-interface OrderedSet { '@type': 'int-ordered-set' }
+interface OrderedSet { '@type': 'int-interval' | 'int-sorted-array' }
 
 export default OrderedSet
\ No newline at end of file
diff --git a/src/mol-base/collections/ordered-set/base.ts b/src/mol-base/collections/ordered-set/base.ts
deleted file mode 100644
index b37bc56e1..000000000
--- a/src/mol-base/collections/ordered-set/base.ts
+++ /dev/null
@@ -1,501 +0,0 @@
-/**
- * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import IntTuple from '../int-tuple'
-import { hash3, hash4 } from '../hash-functions'
-
-type Range = IntTuple
-type SortedArray = ArrayLike<number>
-type OrderedSetImpl = Range | SortedArray
-
-export const Empty: OrderedSetImpl = IntTuple.create(0, -1) as any;
-export function ofSingleton(value: number): OrderedSetImpl { return IntTuple.create(value, value) as any; }
-export function ofRange(min: number, max: number): OrderedSetImpl { return max < min ? Empty : IntTuple.create(min, max) as any; }
-/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
-export function ofSortedArray(xs: SortedArray): OrderedSetImpl {
-    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 as any;
-}
-
-export function size(set: OrderedSetImpl) { return typeof set === 'number' ? sizeR(set) : (set as SortedArray).length; }
-export function has(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set as SortedArray, x); }
-export function indexOf(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set as SortedArray, x); }
-export function getAt(set: OrderedSetImpl, i: number) { return typeof set === 'number' ? elementAtR(set, i) : (set as SortedArray)[i]; }
-export function minValue(set: OrderedSetImpl) { return typeof set === 'number' ? minR(set) : (set as SortedArray)[0]; }
-export function maxValue(set: OrderedSetImpl) { return typeof set === 'number' ? maxR(set) : (set as SortedArray)[(set as SortedArray).length - 1]; }
-
-export function hashCode(set: OrderedSetImpl) {
-    // hash of tuple (size, min, max, mid)
-    const s = size(set);
-    if (!s) return 0;
-    if (s > 2) return hash4(s, getAt(set, 0), getAt(set, s - 1), getAt(set, s >> 1));
-    return hash3(s, getAt(set, 0), getAt(set, s - 1));
-}
-// TODO: possibly add more hash functions to allow for multilevel hashing.
-
-export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return equalRR(a, b);
-        return false;
-    } else if (typeof b === 'number') return false;
-    return equalAA(a as SortedArray, b as SortedArray);
-}
-
-export function areIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) {
-    // 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;
-    return areIntersectingAA(a as SortedArray, b as SortedArray);
-}
-
-/** Check if the 2nd argument is a subset of the 1st */
-export function isSubset(set: OrderedSetImpl, toTest: OrderedSetImpl) {
-    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 as SortedArray, toTest as SortedArray);
-}
-
-export function getPredIndex(set: OrderedSetImpl, x: number) {
-    return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchPredIndex(set as SortedArray, x);
-}
-
-export function getPredIndexInRange(set: OrderedSetImpl, x: number, { start, end }: { start: number, end: number }) {
-    if (typeof set === 'number') {
-        const ret = rangeSearchIndex(set, x);
-        return ret <= start ? start : ret >= end ? end : ret;
-     } else return binarySearchPredIndexRange(set as SortedArray, x, start, end);
-}
-
-export function getIntervalRange(set: OrderedSetImpl, min: number, max: number) {
-    const { start, end } = getStartEnd(set, min, max);
-    return { start, end };
-}
-
-export function union(a: OrderedSetImpl, b: OrderedSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return unionRR(a, b);
-        return unionAR(b as SortedArray, a);
-    } else if (typeof b === 'number') {
-        return unionAR(a as SortedArray, b);
-    } else return unionAA(a as SortedArray, b as SortedArray);
-}
-
-export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) {
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return intersectRR(a, b);
-        return intersectAR(b as SortedArray, a);
-    } else if (typeof b === 'number') {
-        return intersectAR(a as SortedArray, b);
-    } else {
-        if (!areRangesIntersecting(a, b)) return Empty;
-        return intersectAA(a as SortedArray, b as SortedArray);
-    }
-}
-
-export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) {
-    if (!areRangesIntersecting(a, b)) return a;
-
-    if (typeof a === 'number') {
-        if (typeof b === 'number') return substractRR(a, b);
-        return subtractRA(a, b as SortedArray);
-    } else if (typeof b === 'number') {
-        return subtractAR(a as SortedArray, b);
-    } else {
-        return subtractAA(a as SortedArray, b as SortedArray);
-    }
-}
-
-const minR = IntTuple.fst
-const maxR = IntTuple.snd
-const equalRR = IntTuple.areEqual
-
-function sizeR(set: Range) { return maxR(set) - minR(set) + 1; }
-function hasR(set: Range, x: number) { return x >= minR(set) && x <= maxR(set); }
-function indexOfR(set: Range, x: number) { const m = minR(set); return x >= m && x <= maxR(set) ? x - m : -1; }
-function elementAtR(set: Range, i: number) { return IntTuple.fst(set) + i; }
-
-function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; }
-function indexOfA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] ? binarySearch(set, x) : -1; }
-
-function binarySearch(xs: SortedArray, value: number) {
-    return binarySearchRange(xs, value, 0, xs.length);
-}
-
-function binarySearchRange(xs: SortedArray, value: number, start: number, end: number) {
-    let min = start, max = end - 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 binarySearchPredIndex(xs: SortedArray, value: number) {
-    return binarySearchPredIndexRange(xs, value, 0, xs.length);
-}
-
-function binarySearchPredIndexRange(xs: SortedArray, value: number, start: number, end: number) {
-    let min = start, max = end - 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;
-}
-
-function rangeSearchIndex(r: Range, value: number) {
-    const min = minR(r);
-    if (value < min) return 0;
-    const max = maxR(r);
-    if (value > max) return max - min + 1;
-    return value - min;
-}
-
-const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
-function getMaxIntersectionRange(xs: SortedArray, ys: SortedArray) {
-    _maxIntRangeRet.startI = binarySearchPredIndex(xs, ys[0]);
-    _maxIntRangeRet.startJ = binarySearchPredIndex(ys, xs[0]);
-    _maxIntRangeRet.endI = binarySearchPredIndex(xs, ys[ys.length - 1] + 1);
-    _maxIntRangeRet.endJ = binarySearchPredIndex(ys, xs[xs.length - 1] + 1);
-
-    return _maxIntRangeRet;
-}
-
-const _startEndRet = { start: 0, end: 0 };
-function getStartEnd(set: OrderedSetImpl, min: number, max: number) {
-    _startEndRet.start = getPredIndex(set, min);
-    _startEndRet.end = getPredIndex(set, max + 1);
-    return _startEndRet;
-}
-
-function equalAA(a: SortedArray, b: SortedArray) {
-    if (a === b) return true;
-    const aSize = a.length;
-    if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false;
-    for (let i = 0; i < aSize; i++) {
-        if (a[i] !== b[i]) return false;
-    }
-    return true;
-}
-
-function areIntersectingAA(xs: SortedArray, ys: SortedArray) {
-    if (xs === ys) return true;
-
-    let { startI: i, startJ: j, endI, endJ } = getMaxIntersectionRange(xs, ys);
-    while (i < endI && j < endJ) {
-        const x = xs[i], y = ys[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else return true;
-    }
-    return false;
-}
-
-function isSubsetAA(a: SortedArray, b: SortedArray) {
-    if (a === b) return true;
-
-    const lenB = b.length;
-    let { startI: i, startJ: j, endI, endJ } = getMaxIntersectionRange(a, b);
-    // must be able to advance by lenB elements
-    if (endJ - j < lenB || endI - i < lenB) return false;
-
-    let equal = 0;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { i++; j++; equal++; }
-    }
-    return equal === lenB;
-}
-
-function areRangesIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) {
-    return size(a) > 0 && size(b) > 0 && maxValue(a) >= minValue(b) && minValue(a) <= maxValue(b);
-}
-
-function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) {
-    if (!size(a)) return size(b) === 0;
-    if (!size(b)) return true;
-    return minValue(a) <= minValue(b) && maxValue(a) >= maxValue(b);
-}
-
-function unionRR(a: Range, b: Range) {
-    if (IntTuple.areEqual(a, b)) return a;
-
-    const sizeA = sizeR(a), sizeB = sizeR(b);
-    if (!sizeA) return b;
-    if (!sizeB) return a;
-    const minA = minR(a), minB = minR(b);
-    if (areRangesIntersecting(a, b)) return 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 ofSortedArray(arr);
-}
-
-function unionAR(a: SortedArray, b: Range) {
-    const bSize = size(b);
-    if (!bSize) return a;
-    // is the array fully contained in the range?
-    if (isRangeSubset(b, a)) return b;
-
-    const min = minR(b), max = maxR(b);
-    const { start, end } = getStartEnd(a, min, max);
-
-    const indices = new Int32Array(start + (a.length - end) + bSize);
-    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 ofSortedArray(indices);
-}
-
-function unionAA(a: SortedArray, b: SortedArray) {
-    if (a === b) return a;
-
-    const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b);
-    let i = sI, j = sJ;
-    let commonCount = 0;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { i++; j++; commonCount++; }
-    }
-
-    const lenA = a.length, lenB = b.length;
-    // A === B || B is subset of A ==> A
-    if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a;
-    // A is subset of B ===> B
-    if (commonCount === lenA) return b;
-
-    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 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++] = b[k];
-
-    // insert the common part
-    i = sI;
-    j = sJ;
-    while (i < endI && j < endJ) {
-        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 ofSortedArray(indices);
-}
-
-function intersectRR(a: Range, b: Range) {
-    if (!areRangesIntersecting(a, b)) return Empty;
-    if (IntTuple.areEqual(a, b)) return a;
-    return ofRange(Math.max(minR(a), minR(b)), Math.min(maxR(a), maxR(b)));
-}
-
-function intersectAR(a: SortedArray, r: Range) {
-    if (!size(r)) return Empty;
-
-    const { start, end } = getStartEnd(a, minR(r), maxR(r));
-    const resultSize = end - start;
-    if (!resultSize) return Empty;
-
-    const indices = new Int32Array(resultSize);
-    let offset = 0;
-    for (let i = start; i < end; i++) {
-        indices[offset++] = a[i];
-    }
-    return ofSortedArray(indices);
-}
-
-function intersectAA(a: SortedArray, b: SortedArray) {
-    if (a === b) return a;
-
-    const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b);
-    let i = sI, j = sJ;
-    let commonCount = 0;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { i++; j++; commonCount++; }
-    }
-
-    const lenA = a.length, lenB = b.length;
-    // no common elements
-    if (!commonCount) return Empty;
-    // A === B || B is subset of A ==> B
-    if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b;
-    // A is subset of B ==> A
-    if (commonCount === lenA) return a;
-
-    const indices = new Int32Array(commonCount);
-    let offset = 0;
-    i = sI;
-    j = sJ;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { indices[offset++] = x; i++; j++; }
-    }
-
-    return ofSortedArray(indices);
-}
-
-function substractRR(a: Range, b: Range) {
-    if (IntTuple.areEqual(a, b)) return Empty;
-
-    const minA = minR(a), maxA = maxR(a);
-    const minB = minR(b), maxB = maxR(b);
-
-    if (maxA < minA || maxB < minB) return a;
-    // is A subset of B? ==> Empty
-    if (isRangeSubset(b, a)) return Empty;
-    if (isRangeSubset(a, b)) {
-        // this splits the interval into two, gotta represent it as a set.
-        const l = minB - minA, r = maxA - maxB;
-        if (l <= 0) return ofRange(maxB + 1, maxB + r);
-        if (r <= 0) return ofRange(minA, minA + l - 1);
-        const ret = new Int32Array(l + r);
-        let offset = 0;
-        for (let i = 0; i < l; i++) ret[offset++] = minA + i;
-        for (let i = 1; i <= r; i++) ret[offset++] = maxB + i;
-        return 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 (minA < minB) return ofRange(minA, minB - 1);
-    return ofRange(maxB + 1, maxA);
-}
-
-function subtractAR(a: SortedArray, b: Range) {
-    // is empty?
-    const min = minR(b), max = maxR(b);
-    if (max < min) return a;
-
-    const { start, end } = getStartEnd(a, min, max);
-    const resultSize = a.length - (end - start);
-    // A is subset of B
-    if (resultSize <= 0) return Empty;
-    // No common elements
-    if (resultSize === a.length) return a;
-
-    const ret = new Int32Array(resultSize);
-    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 ofSortedArray(ret);
-}
-
-function subtractRA(a: Range, b: SortedArray) {
-    const min = minR(a), max = maxR(a);
-
-    // is empty?
-    if (max < min) return a;
-
-    const rSize = max - min + 1;
-    const { start, end } = getStartEnd(b, min, max);
-    const commonCount = end - start;
-
-    // No common elements.
-    if (commonCount === 0) return a;
-
-    const resultSize = rSize - commonCount;
-    // A is subset of B
-    if (resultSize <= 0) return 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 (binarySearchRange(b, i, start, end) < 0) ret[offset++] = i;
-    }
-    for (let i = last + 1; i <= max; i++) ret[offset++] = i;
-    return ofSortedArray(ret);
-}
-
-function subtractAA(a: SortedArray, b: SortedArray) {
-    if (a === b) return Empty;
-
-    const lenA = a.length;
-
-    const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b);
-    let i = sI, j = sJ;
-    let commonCount = 0;
-    while (i < endI && j < endJ) {
-        const x = a[i], y = b[j];
-        if (x < y) { i++; }
-        else if (x > y) { j++; }
-        else { i++; j++; commonCount++; }
-    }
-
-    // A isnt intersecting B ===> A
-    if (!commonCount) return a;
-    // A === B || A is subset of B ===> Empty
-    if (commonCount >= lenA) return 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 < endI && j < endJ) {
-        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 ofSortedArray(indices);
-}
\ No newline at end of file
diff --git a/src/mol-base/collections/segmentation.ts b/src/mol-base/collections/segmentation.ts
new file mode 100644
index 000000000..a270e7a23
--- /dev/null
+++ b/src/mol-base/collections/segmentation.ts
@@ -0,0 +1,26 @@
+/**
+ * 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'
+import Interval from './interval'
+import OrderedSet from './ordered-set'
+import SortedArray from './sorted-array'
+import * as Impl from './impl/segmentation'
+
+namespace Segmentation {
+    export interface Segment { index: number, start: number, end: number }
+
+    export const create: (segs: SortedArray, segIndex: ArrayLike<number>) => Segmentation = Impl.create as any;
+
+    export const getSegment: (segs: Segmentation, value: number) => number = Impl.getSegment as any;
+    export const projectValue: (segs: Segmentation, set: OrderedSet, value: number) => Interval = Impl.projectValue as any;
+
+    export const segments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any;
+}
+
+interface Segmentation { '@type': 'segmentation' }
+
+export default Segmentation
\ No newline at end of file
diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts
index 1a22451b8..61ef46bee 100644
--- a/src/perf-tests/sets.ts
+++ b/src/perf-tests/sets.ts
@@ -2,6 +2,8 @@ import * as B from 'benchmark'
 import IntTuple from '../mol-base/collections/int-tuple'
 import OrdSet from '../mol-base/collections/ordered-set'
 import AtomSet from '../mol-data/atom-set'
+import Segmentation from '../mol-base/collections/segmentation'
+import SortedArray from '../mol-base/collections/sorted-array'
 
 export namespace Iteration {
     const U = 1000, V = 2500;
@@ -269,12 +271,12 @@ export namespace Tuples {
 
 export function testSegments() {
     const data = OrdSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]);
-    const segs = OrdSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]);
-    const it = OrdSet.segments(segs, data);
+    const segs = Segmentation.create(SortedArray.ofSortedArray([0, 4, 10, 12, 13, 15, 25]), []);
+    const it = Segmentation.segments(segs, data);
 
     for (let s = it.move(); !it.done; s = it.move()) {
         for (let j = s.start; j < s.end; j++) {
-            console.log(`${s.segment}: ${OrdSet.getAt(data, j)}`);
+            console.log(`${s.index}: ${OrdSet.getAt(data, j)}`);
         }
     }
 }
-- 
GitLab