Browse Source

started collections

David Sehnal 7 years ago
parent
commit
d661fe6472

+ 25 - 439
src/perf-tests/sort.ts

@@ -1,458 +1,44 @@
-// array A[] has the items to sort; array B[] is a work array
-function BottomUpMergeSort(A: number[]) {
-    const n = A.length;
-    let src = A, target = new (A as any).constructor(n) as any;
-    // Each 1-element run in A is already "sorted".
-    // Make successively longer sorted runs of length 2, 4, 8, 16... until whole array is sorted.
-    for (let width = 1; width < n; width = 2 * width) {
-        // Array A is full of runs of length width.
-        for (let i = 0; i < n; i = i + 2 * width) {
-            // Merge two runs: A[i:i+width-1] and A[i+width:i+2*width-1] to B[]
-            // or copy A[i:n-1] to B[] ( if(i+width >= n) )
-            BottomUpMerge(src, i, Math.min(i + width, n), Math.min(i + 2 * width, n), target);
-        }
-        // Now work array B is full of runs of length 2*width.
-        // Copy array B to array A for next iteration.
-        // A more efficient implementation would swap the roles of A and B.
-        const t = src;
-        src = target;
-        target = t;
-        // Now array A is full of runs of length 2*width.
-    }
-    if (src !== A) {
-        for (let i = 0; i < n; i++) src[i] = target[i];
-    }
-}
-
-//  Left run is A[iLeft :iRight-1].
-// Right run is A[iRight:iEnd-1  ].
-function BottomUpMerge(A: number[], iLeft: number, iRight: number, iEnd: number, B: number[]) {
-    let i = iLeft, j = iRight;
-    // While there are elements in the left or right runs...
-    for (let k = iLeft; k < iEnd; k++) {
-        // If left run head exists and is <= existing right run head.
-        const u = A[i], v = A[j];
-        if (i < iRight && (j >= iEnd || u <= v)) {
-            B[k] = u;
-            i = i + 1;
-        } else {
-            B[k] = v;
-            j = j + 1;
-        }
-    }
-}
-
-function mergeSort(a: ArrayLike<number>) {
-    BottomUpMergeSort(a as any);
-    return a;
-}
-
-function heapify(arr: number[], n: number, i: number) {
-    const l = 2 * i + 1;  // left = 2*i + 1
-    const r = 2 * i + 2;  // right = 2*i + 2
-
-    let largest = i;  // Initialize largest as root
-
-    // If left child is larger than root
-    if (l < n && arr[l] > arr[largest])
-        largest = l;
-
-    // If right child is larger than largest so far
-    if (r < n && arr[r] > arr[largest])
-        largest = r;
-
-    // If largest is not root
-    if (largest !== i) {
-        //swap(arr[i], arr[largest]);
-        const t = arr[i];
-        arr[i] = arr[largest];
-        arr[largest] = t;
-
-        // Recursively heapify the affected sub-tree
-        heapify(arr, n, largest);
-    }
-}
-
-// main function to do heap sort
-function heapSort(arr: number[]) {
-    const n = arr.length;
-    // Build heap (rearrange array)
-    for (let i = n / 2 - 1; i >= 0; i--)
-        heapify(arr, n, i);
-
-    // One by one extract an element from heap
-    for (let i = n - 1; i >= 0; i--) {
-        // Move current root to end
-        //swap(arr[0], arr[i]);
-        const t = arr[0];
-        arr[0] = arr[i];
-        arr[i] = t;
-
-        // call max heapify on the reduced heap
-        heapify(arr, i, 0);
-    }
-    return arr;
-}
+import * as B from 'benchmark'
+import * as Sort from '../structure/collections/sort'
 
 function createTestData(n: number) {
-    const data = []; //new Int32Array(n); //new Array(n);
+    const data = new Int32Array(n); //new Array(n);
     for (let i = 0; i < n; i++) {
         data[i] = (n * Math.random()) | 0;
     }
     return data;
-
-    //return [ 5, 9, 10, 5, 7, 6, 8, 7, 0, 0, 0, 1, 2, 3, 4 ];
-}
-
-let swapCount = 0;
-function swap(arr: number[], i: number, j: number) {
-    const temp = arr[i];
-    arr[i] = arr[j];
-    arr[j] = temp;
-}
-
-function medianPivot(arr: number[], left: number, right: number) {
-    const l = arr[left], r = arr[right], m = arr[(left + right) >> 1];
-    if (l > r) return l > m ? Math.max(m, r) : l;
-    else return r > m ? Math.max(m, l) : r;
-}
-
-const _qsParts = [0, 0];
-
-function partition3(xs: number[], l: number, r: number) {
-    const v = medianPivot(xs, l, r)
-    let pivot = l, equals = l;
-    for (let i = l; i <= r; i++) {
-        const t = xs[i];
-        if (t < v) {
-            /*if (pivot !== i)*/ swap(xs, i, pivot);
-            pivot++;
-        } else if (t === v) {
-            swap(xs, i, pivot);
-            swap(xs, pivot, equals);
-            equals++;
-            pivot++;
-        }
-    }
-    for (let i = l; i < equals; i++) {
-        swap(xs, i, l + pivot - i - 1)
-    }
-    _qsParts[0] = pivot - equals + l;
-    _qsParts[1] = pivot - 1;
 }
 
-function partition3_1(xs: number[], l: number, r: number) {
-    'use strict';
-    const v = medianPivot(xs, l, r);
-    // console.log('P', v);
-    // console.log('I', xs.slice(l, r + 1));
-    let equals = l, tail = r;
-
-    while (xs[tail] > v)--tail;
-    for (let i = l; i <= tail; i++) {
-        const t = xs[i];
-        if (t > v) {
-            swap(xs, i, tail);
-            tail--;
-            while (xs[tail] > v)--tail;
-            i--;
-        } else if (t === v) {
-            swap(xs, i, equals);
-            equals++;
-        }
-    }
-    //console.log('M', xs.slice(l, r + 1), equals, tail);
-    for (let i = l; i < equals; i++) {
-        swap(xs, i, l + tail - i)
-    }
-    //console.log('F', xs.slice(l, r + 1), tail - equals + l + 1, tail);
-    _qsParts[0] = tail - equals + l + 1;
-    _qsParts[1] = tail;
+export function copyArray(xs: any) {
+    const ret = new xs.constructor(xs.length);
+    for (let i = 0, _i = xs.length; i < _i; i++) ret[i] = xs[i];
+    return ret;
 }
 
-function insertionSort(xs: number[], start: number, end: number) {
-    'use strict';
-
-    for (let i = start + 1; i <= end; i++) {
-        const key = xs[i];
-        let j = i - 1;
-        while (j >= 0 && xs[j] > key) {
-            xs[j + 1] = xs[j];
-            j = j - 1;
-        }
-        xs[j + 1] = key;
-    }
-}
-
-function quickSort(xs: number[], low: number, high: number) {
-    'use strict';
-
-    while (low < high) {
-        if (high - low < 16) {
-            insertionSort(xs, low, high);
-            return;
-        }
-
-        partition3_1(xs, low, high);
-        const li = _qsParts[0], ri = _qsParts[1];
-
-        if (li - low < high - ri) {
-            quickSort(xs, low, li - 1);
-            low = ri + 1;
-        } else { // Else recur for right part
-            quickSort(xs, ri + 1, high);
-            high = li - 1;
-        }
-    }
-}
-
-function checkSorted(arr: ArrayLike<number>) {
+export function checkSorted(arr: ArrayLike<number>) {
     for (let i = 0; i < arr.length - 1; i++) {
         if (arr[i] > arr[i + 1]) {
-            console.log('not sorted');
-            return;
+            return false;
         }
     }
-    console.log('sorted');
+    return true;
 }
 
-function defaultCmp(a: number, b: number) {
-    if (a > b) return 1
-    if (a < b) return -1
-    return 0
-}
-
-function quicksortCmp(arr: number[], cmp: any, bb: number, ee: number) {
-    cmp = cmp || defaultCmp
-    var begin = bb || 0
-    var end = (ee || arr.length) - 1
-
-    var stack = []
-    var sp = -1
-    var left = begin
-    var right = end
-    var tmp = 0.0
-    //var tmp2 = 0.0
-
-    // function swap(a: number, b: number) {
-    //     tmp2 = arr[a]
-    //     arr[a] = arr[b]
-    //     arr[b] = tmp2
-    // }
-
-    var i, j
-
-    while (true) {
-        if (right - left <= 25) {
-            for (j = left + 1; j <= right; ++j) {
-                tmp = arr[j]
-                i = j - 1
-
-                while (i >= left && cmp(arr[i], tmp) > 0) {
-                    arr[i + 1] = arr[i]
-                    --i
-                }
-
-                arr[i + 1] = tmp
-            }
-
-            if (sp === -1) break
-
-            right = stack[sp--] // ?
-            left = stack[sp--]
-        } else {
-            var median = (left + right) >> 1
-
-            i = left + 1
-            j = right
-
-            swap(arr, median, i)
-
-            if (cmp(arr[left], arr[right]) > 0) {
-                swap(arr, left, right)
-            }
-
-            if (cmp(arr[i], arr[right]) > 0) {
-                swap(arr, i, right)
-            }
-
-            if (cmp(arr[left], arr[i]) > 0) {
-                swap(arr, left, i)
-            }
-
-            tmp = arr[i]
-
-            while (true) {
-                do i++; while (cmp(arr[i], tmp) < 0)
-                do j--; while (cmp(arr[j], tmp) > 0)
-                if (j < i) break
-                swap(arr, i, j)
-            }
-
-            arr[left + 1] = arr[j]
-            arr[j] = tmp
-
-            if (right - i + 1 >= j - left) {
-                stack[++sp] = i
-                stack[++sp] = right
-                right = j - 1
-            } else {
-                stack[++sp] = left
-                stack[++sp] = j - 1
-                left = i
-            }
-        }
-    }
-
-    return arr
-}
-
-(function test() {
-    // console.log(medianPivot([1, 2, 3], 0, 2))
-    // console.log(medianPivot([1, 3, 2], 0, 2))
-    // console.log(medianPivot([2, 1, 3], 0, 2))
-    // console.log(medianPivot([3, 1, 2], 0, 2))
-    // console.log(medianPivot([2, 3, 1], 0, 2))
-    // console.log(medianPivot([3, 2, 1], 0, 2))
-
-    const n = 1000;
-
-    Array.prototype.sort.call(createTestData(n), (a: number, b: number) => a - b);
-    mergeSort(createTestData(n));
-
-    let sd;
-
-
-
-    sd = createTestData(n);
-    quickSort(sd as any, 0, sd.length - 1);
-
-    sd = createTestData(n);
-    quicksortCmp(sd as any, void 0, 0, sd.length - 1);
-
-    //console.log(sd);
-
-    // sd = createTestData(n);
-    // console.time('merge');
-    // mergeSort(sd);
-    // console.timeEnd('merge');
-    // checkSorted(sd);
-
-    sd = createTestData(n);
-
-    checkSorted(sd);
-    console.time('heap');
-    heapSort(sd as any);
-    console.timeEnd('heap');
-    checkSorted(sd);
-
-    console.time('heap-sorted');
-    heapSort(sd as any);
-    console.timeEnd('heap-sorted');
-    checkSorted(sd);
-
-    console.log('--------------');
-
-    //console.log('quick', sd);
-
-    sd = createTestData(n);
-    checkSorted(sd);
-    console.time('qs');
-    quickSort(sd as any, 0, sd.length - 1);
-    console.timeEnd('qs');
-    checkSorted(sd);
-
-    console.time('qs-sorted');
-    quickSort(sd as any, 0, sd.length - 1);
-    console.timeEnd('qs-sorted');
-    checkSorted(sd);
-
-    let reverseSorted = new Int32Array(n);
-    for (let i = 0; i < n; i++) {
-        reverseSorted[i] = sd[n - i - 1];
-    }
-
-    console.time('qs-reverse-sorted');
-    quickSort(reverseSorted as any, 0, reverseSorted.length - 1);
-    console.timeEnd('qs-reverse-sorted');
-    checkSorted(reverseSorted);
-
-    reverseSorted = new Int32Array(n);
-    for (let i = 0; i < n; i++) {
-        reverseSorted[i] = sd[n - i - 1];
-    }
-
-    console.time('qs-reverse-sorted');
-    quickSort(reverseSorted as any, 0, reverseSorted.length - 1);
-    console.timeEnd('qs-reverse-sorted');
-    checkSorted(reverseSorted);
-
-    sd = createTestData(n);
-    checkSorted(sd);
-    console.time('qs');
-    quickSort(sd as any, 0, sd.length - 1);
-    console.timeEnd('qs');
-    checkSorted(sd);
-
-    console.log('swap count', swapCount);
-
-    console.log('--------------');
-
-    //console.log('quick', sd);
-
-    sd = createTestData(n);
-    checkSorted(sd);
-    console.time('qs-a');
-    quicksortCmp(sd as any, void 0, 0, sd.length - 1);
-    console.timeEnd('qs-a');
-    checkSorted(sd);
-
-    console.time('qs-a-sorted');
-    quicksortCmp(sd as any, void 0, 0, sd.length - 1);
-    console.timeEnd('qs-a-sorted');
-    checkSorted(sd);
-
-    reverseSorted = new Int32Array(n);
-    for (let i = 0; i < n; i++) {
-        reverseSorted[i] = sd[n - i - 1];
-    }
-
-    console.time('qs-a-reverse-sorted');
-    quicksortCmp(reverseSorted as any, void 0, 0, reverseSorted.length - 1);
-    console.timeEnd('qs-a-reverse-sorted');
-    checkSorted(reverseSorted);
-
-    reverseSorted = new Int32Array(n);
-    for (let i = 0; i < n; i++) {
-        reverseSorted[i] = sd[n - i - 1];
-    }
-
-    console.time('qs-a-reverse-sorted');
-    quicksortCmp(reverseSorted as any, void 0, 0, reverseSorted.length - 1);
-    console.timeEnd('qs-a-reverse-sorted');
-    checkSorted(reverseSorted);
-
-    sd = createTestData(n);
-    checkSorted(sd);
-    console.time('qs-a');
-    quicksortCmp(sd as any, void 0, 0, sd.length - 1);
-    console.timeEnd('qs-a');
-    //console.log(sd);
-    checkSorted(sd);
-
-    console.log('--------------');
+const _data = createTestData(10000);
+const data = () => copyArray(_data);
 
-    sd = createTestData(n);
-    checkSorted(sd);
-    console.time('native');
-    sd.sort((a: number, b: number) => a - b);
-    console.timeEnd('native');
-    checkSorted(sd);
+const suite = new B.Suite();
 
-    console.time('native-sorted');
-    sd.sort((a: number, b: number) => a - b);
-    console.timeEnd('native-sorted');
-    checkSorted(sd);
-    //console.log(sd);
+function le(x: number, y: number) { return x - y; }
 
-}())
+suite
+    .add('native', () => Array.prototype.sort.call(data(), le))
+    .add('qsort (array asc)', () => Sort.sortArray(data()))
+    .add('qsort (generic)', () => Sort.sort(data(), _data.length, Sort.arrayLess, Sort.arraySwap))
+    .add('native sorted', () => Array.prototype.sort.call(_data, le))
+    .add('qsort sorted (array asc)', () => Sort.sortArray(_data))
+    .add('qsort sorted (generic)', () => Sort.sort(_data, _data.length, Sort.arrayLess, Sort.arraySwap))
+    .on('cycle', (e: any) => {
+        console.log(String(e.target));
+    })
+    .run();

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

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

+ 31 - 0
src/structure/collections/int-pair.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+interface IntPair { fst: number, snd: number }
+
+namespace IntPair {
+    const { _int32, _float64 } = (function() {
+        const data = new ArrayBuffer(8);
+        return { _int32: new Int32Array(data), _float64: new Float64Array(data) };
+    }());
+
+    export function zero(): IntPair { return { fst: 0, snd: 0 }; }
+
+    export function set(fst: number, snd: number): number {
+        _int32[0] = fst;
+        _int32[1] = snd;
+        return _float64[0];
+    }
+
+    export function get(packed: number, target: IntPair): IntPair {
+        _float64[0] = packed;
+        target.fst = _int32[0];
+        target.snd = _int32[1];
+        return target;
+    }
+}
+
+export default IntPair

+ 118 - 0
src/structure/collections/iterator.ts

@@ -0,0 +1,118 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+/**
+ * ES6 compatible mutable iterator. Use with care. 
+ *
+ * "Idiomatic" usage:
+ *
+ * const it = ...;
+ * for (let v = it.reset(data).nextValue(); !it.done; v = it.nextValue()) { ... }
+ */
+interface Iterator<T, Data = any> {
+    [Symbol.iterator](): Iterator<T, Data>,
+    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
+    [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
+    private yielded = false;
+
+    [Symbol.iterator]() { return this; }
+    done = false;
+    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
+    private xs: ArrayLike<T> = [];
+    private index: number = -1;
+    private length: number = 0;
+
+    [Symbol.iterator]() { return this; };
+    done = true;
+    value: T = void 0 as any;
+
+    next() {
+        const index = ++this.index;
+        if (index < this.length) this.value = this.xs[index];
+        else this.done = true;
+        return this;
+    }
+
+    nextValue() { return this.next().value; }
+
+    reset(xs: ArrayLike<T>) {
+        this.length = xs.length;
+        this.done = false;
+        this.xs = xs;
+        this.index = -1;
+        return this;
+    }
+}
+
+type Range = { min: number, max: number }
+class __RangeIterator implements Iterator<number, Range> { // tslint:disable-line:class-name
+    private min: number;
+    private max: number;
+
+    [Symbol.iterator]() { return this; };
+    done = true;
+    value: number;
+
+    next() {
+        ++this.value;
+        this.done = this.value >= this.max;
+        return this;
+    }
+
+    nextValue() { return this.next().value;  }
+
+    reset({ min, max}: Range) {
+        this.min = min;
+        this.max = max;
+        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 }); }
+
+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

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

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

+ 145 - 0
src/structure/collections/sort.ts

@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export type Comparer<T = any> = (data: T, i: number, j: number) => number
+export type Swapper<T = any> = (data: T, i: number, j: number) => void
+
+type Ctx = {
+    cmp: Comparer,
+    swap: Swapper,
+    parts: number[],
+    data: any
+}
+
+export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
+    return arr[i] - arr[j];
+}
+
+export function arraySwap(arr: any[], i: number, j: number) {
+    const temp = arr[i];
+    arr[i] = arr[j];
+    arr[j] = temp;
+}
+
+function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {
+    const m = (l + r) >> 1;
+    if (cmp(data, l, r) > 0) return cmp(data, l, m) > 0 ? cmp(data, m, r) > 0 ? m : r : l;
+    else return cmp(data, r, m) > 0 ? cmp(data, m, l) > 0 ? m : l : r;
+}
+
+function partitionGeneric(ctx: Ctx, l: number, r: number) {
+    const { cmp, swap, data, parts } = ctx;
+    let equals = l + 1, tail = r;
+
+    // move the median to the 1st spot
+    swap(data, l, medianPivotIndex(data, cmp, l, r));
+
+    while (cmp(data, tail, l) > 0) { --tail; }
+    for (let i = l + 1; i <= tail; i++) {
+        const c = cmp(data, i, l);
+        if (c > 0) {
+            swap(data, i, tail);
+            --tail;
+            while (cmp(data, tail, l) > 0) { --tail; }
+            i--;
+        } else if (c === 0) {
+            swap(data, i, equals);
+            equals++;
+        }
+    }
+
+    // move the medians to the correct spots
+    for (let i = l; i < equals; i++) { swap(data, i, l + tail - i); }
+    parts[0] = tail - equals + l + 1;
+    parts[1] = tail;
+}
+
+function partitionArrayAsc(data: number[], parts: number[], l: number, r: number) {
+    let equals = l + 1, tail = r;
+
+    // move the median to the 1st spot
+    arraySwap(data, l, medianPivotIndex(data, arrayLess, l, r));
+    const pivot = data[l];
+
+    while (data[tail] > pivot) { --tail; }
+    for (let i = l + 1; i <= tail; i++) {
+        const v = data[i];
+        if (v > pivot) {
+            arraySwap(data, i, tail);
+            --tail;
+            while (data[tail] > pivot) { --tail; }
+            i--;
+        } else if (v === pivot) {
+            arraySwap(data, i, equals);
+            ++equals;
+        }
+    }
+
+    // move all medians to the correct spots
+    for (let i = l; i < equals; i++) { arraySwap(data, i, l + tail - i); }
+    parts[0] = tail - equals + l + 1;
+    parts[1] = tail;
+}
+
+function quickSort(ctx: Ctx, low: number, high: number) {
+    const { parts } = ctx;
+    while (low < high) {
+        partitionGeneric(ctx, low, high);
+        const li = parts[0], ri = parts[1];
+
+        if (li - low < high - ri) {
+            quickSort(ctx, low, li - 1);
+            low = ri + 1;
+        } else {
+            quickSort(ctx, ri + 1, high);
+            high = li - 1;
+        }
+    }
+}
+
+function insertionSort(data: number[], start: number, end: number) {
+    for (let i = start + 1; i <= end; i++) {
+        const key = data[i];
+        let j = i - 1;
+        while (j >= 0 && data[j] > key) {
+            data[j + 1] = data[j];
+            j = j - 1;
+        }
+        data[j + 1] = key;
+    }
+}
+
+function quickSortArrayAsc(data: number[], parts: number[], low: number, high: number) {
+    while (low < high) {
+        if (high - low < 16) {
+            insertionSort(data, low, high);
+            return;
+        }
+
+        partitionArrayAsc(data, parts, low, high);
+        const li = parts[0], ri = parts[1];
+
+        if (li - low < high - ri) {
+            quickSortArrayAsc(data, parts, low, li - 1);
+            low = ri + 1;
+        } else {
+            quickSortArrayAsc(data, parts, ri + 1, high);
+            high = li - 1;
+        }
+    }
+}
+
+export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
+    if (cmp === arrayLess) quickSortArrayAsc(data as any, [0, 0], 0, data.length - 1);
+    else quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, 0, data.length - 1);
+    return data;
+}
+
+export function sort<T>(data: T, count: number, cmp: Comparer<T>, swap: Swapper<T>): T {
+    const ctx: Ctx = { data, cmp, swap, parts: [0, 0] };
+    quickSort(ctx, 0, count - 1);
+    return data;
+}

+ 120 - 0
src/structure/spec/collections.spec.ts

@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import Iterator, * as I from '../collections/iterator'
+import IntPair from '../collections/int-pair'
+import * as Sort from '../collections/sort'
+
+describe('basic iterators', () => {
+    function check<T>(name: string, iter: Iterator<T>, expected: T[]) {
+        it(name, () => {
+            expect(I.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]);
+});
+
+describe('int pair', () => {
+    it('works', () => {
+        const p = IntPair.zero();
+        for (let i = 0; i < 10; i++) {
+            for (let j = -10; j < 5; j++) {
+                const t = IntPair.set(i, j);
+                IntPair.get(t, p);
+                expect(p.fst).toBe(i);
+                expect(p.snd).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 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+    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 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+    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), data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
+                }
+            } else {
+                expect(Sort.sort([...data], 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), len, cmp, swap)).toEqual(data);
+                }
+            } else {
+                expect(Sort.sort(clone(src), len, cmp, swap)).toEqual(data);
+            }
+        });
+    }
+    test('sorted', data, false);
+    test('shuffled', data, true);
+})