diff --git a/src/structure/collections/iterator.ts b/src/structure/collections/iterator.ts
index 2fc26b10b3bf2f955b21ba3076a7ceebac06f0b0..4e6072234a90439ed573a5d2a7dbca45eafd91f5 100644
--- a/src/structure/collections/iterator.ts
+++ b/src/structure/collections/iterator.ts
@@ -10,28 +10,25 @@
  * "Idiomatic" usage:
  *
  * const it = ...;
- * for (let v = it.reset(data).nextValue(); !it.done; v = it.nextValue()) { ... }
+ * for (let v = it.nextValue(); !it.done; v = it.nextValue()) { ... }
  */
-interface Iterator<T, Data = any> {
-    [Symbol.iterator](): Iterator<T, Data>,
+interface Iterator<T> {
+    [Symbol.iterator](): Iterator<T>,
     readonly done: boolean,
     readonly value: T,
     next(): { done: boolean, value: T },
-
-    reset(data: Data): Iterator<T, Data>,
     nextValue(): T
 }
 
-class __EmptyIterator implements Iterator<any, any> { // tslint:disable-line:class-name
+class EmptyIteratorImpl implements Iterator<any> {
     [Symbol.iterator]() { return this; }
     done = true;
     value = void 0;
     next() { return this; }
     nextValue() { return this.value; }
-    reset(value: undefined) { return this; }
 }
 
-class __SingletonIterator<T> implements Iterator<T, T> { // tslint:disable-line:class-name
+class SingletonIteratorImpl<T> implements Iterator<T> {
     private yielded = false;
 
     [Symbol.iterator]() { return this; }
@@ -39,13 +36,10 @@ class __SingletonIterator<T> implements Iterator<T, T> { // tslint:disable-line:
     value: T;
     next() { this.done = this.yielded; this.yielded = true; return this; }
     nextValue() { return this.next().value; }
-    reset(value: T) { this.value = value; this.done = false; this.yielded = false; return this; }
-
     constructor(value: T) { this.value = value; }
 }
 
-
-class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disable-line:class-name
+class ArrayIteratorImpl<T> implements Iterator<T> {
     private xs: ArrayLike<T> = [];
     private index: number = -1;
     private length: number = 0;
@@ -63,7 +57,7 @@ class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disabl
 
     nextValue() { return this.next().value; }
 
-    reset(xs: ArrayLike<T>) {
+    constructor(xs: ArrayLike<T>) {
         this.length = xs.length;
         this.done = false;
         this.xs = xs;
@@ -72,47 +66,37 @@ class __ArrayIterator<T> implements Iterator<T, ArrayLike<T>> { // tslint:disabl
     }
 }
 
-type Range = { min: number, max: number }
-class __RangeIterator implements Iterator<number, Range> { // tslint:disable-line:class-name
-    private min: number;
-    private max: number;
-
+class RangeIteratorImpl implements Iterator<number> {
     [Symbol.iterator]() { return this; };
     done = true;
     value: number;
 
     next() {
         ++this.value;
-        this.done = this.value >= this.max;
+        this.done = this.value > this.max;
         return this;
     }
 
     nextValue() { return this.next().value;  }
 
-    reset({ min, max}: Range) {
-        this.min = min;
-        this.max = max;
+    constructor(min: number, private max: number) {
         this.value = min - 1;
         this.done = false;
         return this;
     }
-
-    constructor(bounds: Range) { this.reset(bounds); }
 }
 
-export const EmptyIterator: Iterator<any> = new __EmptyIterator();
-export function SingletonIterator<T>(value: T): Iterator<T, T> { return new __SingletonIterator(value); }
-export function ArrayIterator<T>(xs?: ArrayLike<T>): Iterator<T, ArrayLike<T>> {
-    const ret = new __ArrayIterator<T>();
-    if (xs) ret.reset(xs);
-    return ret;
-}
-export function RangeIterator(bounds?: Range): Iterator<number, Range> { return new __RangeIterator(bounds || { min: 0, max: 0 }); }
+namespace Iterator {
+    export const Empty: Iterator<any> = new EmptyIteratorImpl();
+    export function Singleton<T>(value: T): Iterator<T> { return new SingletonIteratorImpl(value); }
+    export function Array<T>(xs: ArrayLike<T>): Iterator<T> { return new ArrayIteratorImpl<T>(xs); }
+    export function Range(min: number, max: number): Iterator<number> { return new RangeIteratorImpl(min, max); }
 
-export function toArray<T>(it: Iterator<T>): T[] {
-    const ret = [];
-    for (let v = it.nextValue(); !it.done; v = it.nextValue()) ret[ret.length] = v;
-    return ret;
+    export function toArray<T>(it: Iterator<T>): T[] {
+        const ret = [];
+        for (let v = it.nextValue(); !it.done; v = it.nextValue()) ret[ret.length] = v;
+        return ret;
+    }
 }
 
 export default Iterator
\ No newline at end of file
diff --git a/src/structure/collections/linear-set.ts b/src/structure/collections/linear-set.ts
deleted file mode 100644
index 0ffdd02fcbce683e436c0030ffe0517135c6ceda..0000000000000000000000000000000000000000
--- a/src/structure/collections/linear-set.ts
+++ /dev/null
@@ -1 +0,0 @@
-// TODO
\ No newline at end of file
diff --git a/src/structure/collections/range-set.ts b/src/structure/collections/range-set.ts
new file mode 100644
index 0000000000000000000000000000000000000000..93a95a1611bcd970254bf098e86ebcff7fb307eb
--- /dev/null
+++ b/src/structure/collections/range-set.ts
@@ -0,0 +1,228 @@
+/**
+ * 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,
+    elements(): Iterator<number>
+}
+
+namespace RangeSet {
+    interface Impl extends RangeSet {
+        readonly min: number,
+        readonly max: number,
+        toArray(): ArrayLike<number>
+    }
+
+    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;
+        }
+        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; }
+        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 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;
+
+        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;
+
+        let i = 0, j = 0, resultSize = 0;
+        while (i < la && j < lb) {
+            const x = xs[i], y = ys[j];
+            if (x < y) { i++; }
+            else if (x > y) { j++; }
+            else { i++; j++; resultSize++; }
+        }
+
+        if (!resultSize) return Empty;
+
+        const indices = new Int32Array(resultSize);
+        let offset = 0;
+        i = 0;
+        j = 0;
+        while (i < la && j < lb) {
+            const x = xs[i], y = ys[j];
+            if (x < y) { i++; }
+            else if (x > y) { j++; }
+            else { indices[offset++] = x; i++; j++; }
+        }
+
+        return ofSortedArray(indices);
+    }
+}
+
+export default RangeSet
\ No newline at end of file
diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts
index c23fee8decaddb6da83a4f8e631f2bb5a2267a15..57e130ac83d53b26aadfd71d5f568d159a6ddc83 100644
--- a/src/structure/spec/collections.spec.ts
+++ b/src/structure/spec/collections.spec.ts
@@ -4,24 +4,22 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import Iterator, * as I from '../collections/iterator'
+import Iterator from '../collections/iterator'
 import IntPair from '../collections/int-pair'
 import * as Sort from '../collections/sort'
+import RangeSet from '../collections/range-set'
 
 describe('basic iterators', () => {
     function check<T>(name: string, iter: Iterator<T>, expected: T[]) {
         it(name, () => {
-            expect(I.toArray(iter)).toEqual(expected);
+            expect(Iterator.toArray(iter)).toEqual(expected);
         });
     }
 
-    check('empty', I.EmptyIterator, []);
-    check('singleton', I.SingletonIterator(10), [10]);
-    check('singleton reset', I.SingletonIterator(10).reset(13), [13]);
-    check('array', I.ArrayIterator([1, 2, 3]), [1, 2, 3]);
-    check('array reset', I.ArrayIterator([1, 2, 3]).reset([4]), [4]);
-    check('range', I.RangeIterator({ min: 0, max: 3 }), [0, 1, 2]);
-    check('range reset', I.RangeIterator().reset({ min: 1, max: 2 }), [1]);
+    check('empty', Iterator.Empty, []);
+    check('singleton', Iterator.Singleton(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', () => {
@@ -117,4 +115,66 @@ describe('qsort-dual array', () => {
     }
     test('sorted', data, false);
     test('shuffled', data, true);
-})
\ No newline at end of file
+})
+
+describe('range set', () => {
+    function testEq(name: string, set: RangeSet, 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]);
+
+    testEq('empty', empty, []);
+    testEq('singleton', singleton, [10]);
+    testEq('range', range, [1, 2, 3, 4]);
+    testEq('sorted array', arr, [1, 3, 6]);
+
+    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]);
+});
\ No newline at end of file