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