diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts index bdebe3f745c6511b78cd70719a6c790af38656fb..9319c10fae19b1075655c42101d19b374c0acb8c 100644 --- a/src/structure/collections/multi-set.ts +++ b/src/structure/collections/multi-set.ts @@ -90,10 +90,12 @@ namespace MultiSet { return subtractEE(a, b); } - // TODO: union + export function union(a: MultiSet, b: MultiSet): MultiSet { + return findUnion([a, b]); + } - export function union(sets: ArrayLike<MultiSet>): MultiSet { - return 0 as any; + export function unionMany(sets: ArrayLike<MultiSet>): MultiSet { + return findUnion(sets); } class ElementsIterator implements Iterator<IntPair> { @@ -145,7 +147,6 @@ namespace MultiSet { const pair = IntPair.zero(); - function isArrayLike(x: any): x is ArrayLike<number> { return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer)); } @@ -198,6 +199,28 @@ function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet return ret; } +function getUniqueElements(xs: number[]) { + let count = 1; + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] !== xs[i]) count++; + } + const ret = new (xs as any).constructor(count); + ret[0] = xs[0]; + let offset = 1; + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i]; + } + return ret; +} + +function normalizeArray(xs: number[]) { + sortArray(xs); + for (let i = 1, _i = xs.length; i < _i; i++) { + if (xs[i - 1] === xs[i]) return getUniqueElements(xs); + } + return xs; +} + function ofPackedPairs(xs: ArrayLike<number>): MultiSet { if (xs.length === 0) return MultiSet.Empty; const sets: { [key: number]: number[] } = Object.create(null); @@ -213,7 +236,7 @@ function ofPackedPairs(xs: ArrayLike<number>): MultiSet { for (const _k of Object.keys(sets)) { const k = +_k; keys[keys.length] = k; - ret[k] = OrderedSet.ofSortedArray(sortArray(sets[k])); + ret[k] = OrderedSet.ofSortedArray(normalizeArray(sets[k])); } return ofObject1(keys, ret); } @@ -339,4 +362,62 @@ function subtractEE(a: MultiSetElements, b: MultiSetElements) { return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); } +function findUnion(sets: ArrayLike<MultiSet>) { + if (!sets.length) return MultiSet.Empty; + if (sets.length === 1) return sets[0]; + if (sets.length === 2 && sets[0] === sets[1]) return sets[0]; + + const eCount = { count: 0 }; + const ns = unionN(sets, eCount); + if (!eCount.count) return ns; + const ret = Object.create(null); + for (let i = 0, _i = sets.length; i < _i; i++) { + const s = sets[i]; + if (typeof s !== 'number') unionInto(ret, s); + } + if (MultiSet.size(ns) > 0) { + if (typeof ns === 'number') unionIntoN(ret, ns); + else unionInto(ret, ns); + } + return ofObject(ret); +} + +function unionN(sets: ArrayLike<MultiSet>, eCount: { count: number }) { + let countN = 0, countE = 0; + for (let i = 0, _i = sets.length; i < _i; i++) { + if (typeof sets[i] === 'number') countN++; + else countE++; + } + eCount.count = countE; + if (!countN) return MultiSet.Empty; + if (countN === sets.length) return ofPackedPairs(sets as ArrayLike<number>); + const packed = new Float64Array(countN); + let offset = 0; + for (let i = 0, _i = sets.length; i < _i; i++) { + const s = sets[i]; + if (typeof s === 'number') packed[offset++] = s; + } + return ofPackedPairs(packed); +} + +function unionInto(data: { [key: number]: OrderedSet }, a: MultiSetElements) { + const keys = a.keys; + for (let i = 0, _i = keys.size; i < _i; i++) { + const k = keys.elementAt(i); + const set = data[k]; + if (set) data[k] = OrderedSet.union(set, a[k]); + else data[k] = a[k]; + } +} + +function unionIntoN(data: { [key: number]: OrderedSet }, a: number) { + IntPair.unpack(a, pair); + const set = data[pair.fst]; + if (set) { + data[pair.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(pair.snd)); + } else { + data[pair.fst] = OrderedSet.ofSingleton(pair.snd); + } +} + 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 4bf0092baa02ea31d96e30347e59b1225649ff0d..1ad4858020498e97eb88446d1538e63133b50bf9 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -404,4 +404,19 @@ describe('multiset', () => { expect(setToPairs(MultiSet.subtract(c, a))).toEqual([]); expect(setToPairs(MultiSet.subtract(d, a))).toEqual([p(2, 3)]); }); + + it('union', () => { + const a = MultiSet.create([r(1, 3), r(0, 1)]); + const a1 = MultiSet.create([r(1, 3), r(0, 1)]); + const b = MultiSet.create([r(10, 3), r(0, 1)]); + const c = MultiSet.create([r(1, 3)]); + const d = MultiSet.create([r(2, 3)]); + expect(MultiSet.unionMany([a])).toBe(a); + expect(MultiSet.union(a, a)).toBe(a); + expect(setToPairs(MultiSet.union(a, a))).toEqual([p(0, 1), p(1, 3)]); + expect(setToPairs(MultiSet.union(a, a1))).toEqual([p(0, 1), p(1, 3)]); + expect(setToPairs(MultiSet.union(a, b))).toEqual([p(0, 1), p(1, 3), p(10, 3)]); + expect(setToPairs(MultiSet.union(c, d))).toEqual([p(1, 3), p(2, 3)]); + expect(setToPairs(MultiSet.unionMany([a, b, c, d]))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]); + }); }); \ No newline at end of file