From b56828e01575952c055642c8e17e26107244b820 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Sun, 22 Oct 2017 21:59:46 +0200
Subject: [PATCH] multiset

---
 src/structure/collections/int-pair.ts  |  53 ++++++++-
 src/structure/collections/iterator.ts  |  34 +++---
 src/structure/collections/multi-set.ts | 143 +++++++++++++++++++++++++
 src/structure/spec/collections.spec.ts |  49 ++++++++-
 4 files changed, 253 insertions(+), 26 deletions(-)
 create mode 100644 src/structure/collections/multi-set.ts

diff --git a/src/structure/collections/int-pair.ts b/src/structure/collections/int-pair.ts
index 251b145cd..4cdf0199e 100644
--- a/src/structure/collections/int-pair.ts
+++ b/src/structure/collections/int-pair.ts
@@ -7,25 +7,72 @@
 interface IntPair { fst: number, snd: number }
 
 namespace IntPair {
-    const { _int32, _float64 } = (function() {
+    const { _int32, _float64, _int32_1, _float64_1 } = (function() {
         const data = new ArrayBuffer(8);
-        return { _int32: new Int32Array(data), _float64: new Float64Array(data) };
+        const data_1 = new ArrayBuffer(8);
+        return {
+            _int32: new Int32Array(data),
+            _float64: new Float64Array(data),
+            _int32_1: new Int32Array(data_1),
+            _float64_1: new Float64Array(data_1)
+        };
     }());
 
+    export function is(x: any): x is IntPair {
+        return !!x && typeof x.fst === 'number' && typeof x.snd === 'number';
+    }
+
+    export function create(fst: number, snd: number) { return { fst, snd }; }
     export function zero(): IntPair { return { fst: 0, snd: 0 }; }
 
-    export function set(fst: number, snd: number): number {
+    export function set1(fst: number, snd: number): number {
         _int32[0] = fst;
         _int32[1] = snd;
         return _float64[0];
     }
 
+    export function set(p: IntPair): number {
+        _int32[0] = p.fst;
+        _int32[1] = p.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 function get1(packed: number): IntPair {
+        return get(packed, zero());
+    }
+
+    export function fst(packed: number): number {
+        _float64[0] = packed;
+        return _int32[0];
+    }
+
+    export function snd(packed: number): number {
+        _float64[0] = packed;
+        return _int32[1];
+    }
+
+    export function compare(a: number, b: number) {
+        _float64[0] = a;
+        _float64_1[0] = b;
+        const x = _int32[0] - _int32_1[0];
+        if (x !== 0) return x;
+        return _int32[1] - _int32_1[1];
+    }
+
+    export function compareInArray(xs: ArrayLike<number>, i: number, j: number) {
+        _float64[0] = xs[i];
+        _float64_1[0] = xs[j];
+        const x = _int32[0] - _int32_1[0];
+        if (x !== 0) return x;
+        return _int32[1] - _int32_1[1];
+    }
 }
 
 export default IntPair
\ No newline at end of file
diff --git a/src/structure/collections/iterator.ts b/src/structure/collections/iterator.ts
index 79fdbd0f9..269f778ff 100644
--- a/src/structure/collections/iterator.ts
+++ b/src/structure/collections/iterator.ts
@@ -10,11 +10,11 @@
  * "Idiomatic" usage is to use the move function, because it does not make any allocations.
  *
  * const it = ...;
- * for (let v = it.move(); it.hasNext; v = it.move()) { ... }
+ * for (let v = it.move(); !it.done; v = it.move()) { ... }
  */
 interface Iterator<T> {
     [Symbol.iterator](): Iterator<T>,
-    readonly hasNext: boolean,
+    readonly done: boolean,
     next(): { done: boolean, value: T },
     move(): T
 }
@@ -25,24 +25,24 @@ class ArrayIteratorImpl<T> implements Iterator<T> {
     private length: number = 0;
     private lastValue: T;
 
-    [Symbol.iterator]() { return this; };
-    hasNext: boolean;
+    [Symbol.iterator]() { return new ArrayIteratorImpl(this.xs); };
+    done: boolean;
 
     next() {
         const value = this.move();
-        return { value, done: !this.hasNext };
+        return { value, done: this.done };
     }
 
     move() {
         const index = ++this.index;
         if (index < this.length) this.lastValue = this.xs[index];
-        else this.hasNext = false;
+        else this.done = true;
         return this.lastValue;
     }
 
     constructor(xs: ArrayLike<T>) {
         this.length = xs.length;
-        this.hasNext = xs.length > 0;
+        this.done = xs.length === 0;
         this.xs = xs;
         this.index = -1;
         // try to avoid deoptimization with undefined values
@@ -54,23 +54,23 @@ class ArrayIteratorImpl<T> implements Iterator<T> {
 class RangeIteratorImpl implements Iterator<number> {
     private value: number;
 
-    [Symbol.iterator]() { return this; };
-    hasNext: boolean;
+    [Symbol.iterator]() { return new RangeIteratorImpl(this.min, this.max); };
+    done: boolean;
 
     next() {
-        const value = this.value;
-        return { value, done: !this.hasNext }
+        const value = this.move();
+        return { value, done: this.done }
     }
 
     move() {
         ++this.value;
-        this.hasNext = this.value <= this.max;
+        this.done = this.value > this.max;
         return this.value;
     }
 
-    constructor(min: number, private max: number) {
+    constructor(private min: number, private max: number) {
         this.value = min - 1;
-        this.hasNext = max >= min;
+        this.done = max < min;
         return this;
     }
 }
@@ -80,12 +80,6 @@ namespace Iterator {
     export function Array<T>(xs: ArrayLike<T>): Iterator<T> { return new ArrayIteratorImpl<T>(xs); }
     export function Value(value: number): Iterator<number> { return new RangeIteratorImpl(value, value); }
     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.move(); it.hasNext; v = it.move()) ret[ret.length] = v;
-        return ret;
-    }
 }
 
 export default Iterator
\ No newline at end of file
diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts
new file mode 100644
index 000000000..491d6d9ca
--- /dev/null
+++ b/src/structure/collections/multi-set.ts
@@ -0,0 +1,143 @@
+/**
+ * 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 Iterator from './iterator'
+import IntPair from './int-pair'
+import { sortArray } from './sort'
+
+type MultiSetElements = { [id: number]: OrderedSet, keys: OrderedSet }
+type MultiSet = number | MultiSetElements
+
+namespace MultiSet {
+    export const Empty: MultiSet = { keys: OrderedSet.Empty };
+
+    export function keys(set: MultiSet): OrderedSet {
+        if (typeof set === 'number') return OrderedSet.ofSingleton(set);
+        return set.keys;
+    }
+
+    export function hasKey(set: MultiSet, key: number): boolean {
+        if (typeof set === 'number') return IntPair.fst(set) === key;
+        return set.keys.has(key);
+    }
+
+    export function get(set: MultiSet, key: number): OrderedSet {
+        if (!hasKey(set, key)) return OrderedSet.Empty;
+        if (typeof set === 'number') return OrderedSet.ofSingleton(IntPair.snd(set));
+        return set[key];
+    }
+
+    function isArrayLike(x: any): x is ArrayLike<number> {
+        return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer));
+    }
+
+    export function create(data: number | ArrayLike<number> | IntPair | { [id: number]: OrderedSet }): MultiSet {
+        if (typeof data === 'number') return data;
+        if (IntPair.is(data)) return IntPair.set(data);
+        if (isArrayLike(data)) return ofPackedPairs(data);
+        const keys = [];
+        for (const _k of Object.keys(data)) {
+            const k = +_k;
+            if (data[k].size > 0) keys[keys.length] = k;
+        }
+        if (!keys.length) return Empty;
+        sortArray(keys);
+        const ret = Object.create(null);
+        ret.keys = OrderedSet.ofSortedArray(keys);
+        for (const k of keys) ret[k] = data[k];
+        return ret;
+    }
+
+    class PairIterator implements Iterator<IntPair> {
+        private yielded = false;
+        [Symbol.iterator]() { return new PairIterator(this.pair); };
+        done = false;
+        next() { const value = this.move(); return { value, done: this.done } }
+        move() { this.done = this.yielded; this.yielded = true; return this.pair; }
+        constructor(private pair: IntPair) { }
+    }
+
+    class ElementsIterator implements Iterator<IntPair> {
+        private pair = IntPair.zero();
+        private unit = 0;
+        private currentIndex = -1;
+        private currentIterator: Iterator<number> = Iterator.Empty;
+
+        [Symbol.iterator]() { return new ElementsIterator(this.elements); };
+        done: boolean;
+        next() { const value = this.move(); return { value, done: this.done } }
+
+        move() {
+            if (this.done) return this.pair;
+
+            let next = this.currentIterator.move();
+            if (this.currentIterator.done) {
+                if (!this.advanceIterator()) {
+                    this.done = true;
+                    return this.pair;
+                }
+                next = this.currentIterator.move();
+            }
+
+            this.pair.snd = next;
+            return this.pair;
+        }
+
+        private advanceIterator() {
+            const keys = this.elements.keys;
+            if (++this.currentIndex >= keys.size) return false;
+            this.unit = keys.elementAt(this.currentIndex);
+            this.pair.fst = this.unit;
+            this.currentIterator = this.elements[this.unit].elements();
+            return true;
+        }
+
+        constructor(private elements: MultiSetElements) {
+            this.done = elements.keys.size === 0;
+            this.advanceIterator();
+        }
+    }
+
+    export function values(set: MultiSet): Iterator<IntPair> {
+        if (typeof set === 'number') return new PairIterator(IntPair.get1(set));
+        return new ElementsIterator(set);
+    }
+
+    function ofPackedPairs(xs: ArrayLike<number>): MultiSet {
+        if (xs.length === 0) return Empty;
+
+        sortArray(xs, IntPair.compareInArray);
+        const p = IntPair.zero();
+        IntPair.get(xs[0], p);
+        let currentKey = p.fst;
+        let keys = [];
+        let currentSet = [p.snd];
+        let ret = Object.create(null);
+        for (let i = 1, _i = xs.length; i < _i; i++) {
+            IntPair.get(xs[i], p);
+            if (p.fst === currentKey) {
+                currentSet[currentSet.length] = p.snd;
+            } else {
+                ret[currentKey] = OrderedSet.ofSortedArray(currentSet);
+                keys[keys.length] = currentKey;
+
+                currentKey = p.fst;
+                currentSet = [p.snd];
+            }
+        }
+        ret[currentKey] = OrderedSet.ofSortedArray(currentSet);
+        keys[keys.length] = currentKey;
+        ret.keys = OrderedSet.ofSortedArray(keys);
+        return ret;
+    }
+
+    export function union(sets: ArrayLike<MultiSet>): MultiSet {
+        return 0 as any;
+    }
+}
+
+export default MultiSet
\ No newline at end of file
diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts
index 098c84be1..1e6bde4b1 100644
--- a/src/structure/spec/collections.spec.ts
+++ b/src/structure/spec/collections.spec.ts
@@ -9,11 +9,19 @@ import IntPair from '../collections/int-pair'
 import * as Sort from '../collections/sort'
 import OrderedSet from '../collections/ordered-set'
 import LinkedIndex from '../collections/linked-index'
+import MultiSet from '../collections/multi-set'
+
+
+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(Iterator.toArray(iter)).toEqual(expected);
+            expect(iteratorToArray(iter)).toEqual(expected);
         });
     }
 
@@ -28,7 +36,7 @@ describe('int pair', () => {
         const p = IntPair.zero();
         for (let i = 0; i < 10; i++) {
             for (let j = -10; j < 5; j++) {
-                const t = IntPair.set(i, j);
+                const t = IntPair.set1(i, j);
                 IntPair.get(t, p);
                 expect(p.fst).toBe(i);
                 expect(p.snd).toBe(j);
@@ -124,7 +132,7 @@ describe('range set', () => {
     function testEq(name: string, set: OrderedSet, expected: number[]) {
         it(name, () => {
             // copy the arrays to ensure "compatibility" between typed and native arrays
-            expect(Array.prototype.slice.call(Iterator.toArray(set.elements()))).toEqual(Array.prototype.slice.call(expected));
+            expect(Array.prototype.slice.call(iteratorToArray(set.elements()))).toEqual(Array.prototype.slice.call(expected));
         });
     }
 
@@ -259,4 +267,39 @@ describe('linked-index', () => {
         expect(index.has(0)).toBe(false);
         expect(index.has(1)).toBe(false);
     });
+});
+
+describe('multiset', () => {
+    const p = (i: number, j: number) => IntPair.create(i, j);
+    const r = (i: number, j: number) => IntPair.set1(i, j);
+
+    function iteratorPairsToArray(it: Iterator<IntPair>): IntPair[] {
+        const ret = [];
+        for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = IntPair.create(v.fst, v.snd);
+        return ret;
+    }
+
+    it('singleton pair', () => {
+        const set = MultiSet.create(p(10, 11));
+        expect(iteratorPairsToArray(MultiSet.values(set))).toEqual([p(10, 11)]);
+    });
+
+    it('singleton number', () => {
+        const set = MultiSet.create(r(10, 11));
+        expect(iteratorPairsToArray(MultiSet.values(set))).toEqual([p(10, 11)]);
+    });
+
+    it('multi', () => {
+        const set = MultiSet.create({
+            1: OrderedSet.ofSortedArray([4, 6, 7]),
+            3: OrderedSet.ofRange(0, 1),
+        });
+        expect(iteratorPairsToArray(MultiSet.values(set))).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
+    });
+
+    it('packed pairs', () => {
+        const set = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]);
+        expect(iteratorPairsToArray(MultiSet.values(set))).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
+    });
+
 });
\ No newline at end of file
-- 
GitLab