From 8ff4ae7a6e5e9987246f5da596673d7358eb3508 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 23 Oct 2017 13:03:30 +0200 Subject: [PATCH] multiset --- src/structure/collections/hash-functions.ts | 46 ++++ src/structure/collections/int-pair.ts | 7 +- src/structure/collections/multi-set.ts | 288 +++++++++++++++++--- src/structure/collections/ordered-set.ts | 47 +++- src/structure/spec/collections.spec.ts | 87 +++++- 5 files changed, 420 insertions(+), 55 deletions(-) create mode 100644 src/structure/collections/hash-functions.ts diff --git a/src/structure/collections/hash-functions.ts b/src/structure/collections/hash-functions.ts new file mode 100644 index 000000000..542559ae5 --- /dev/null +++ b/src/structure/collections/hash-functions.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +// from http://burtleburtle.net/bob/hash/integer.html +export function hash1(i: number) { + let a = i ^ (i >> 4); + a = (a ^ 0xdeadbeef) + (a << 5); + a = a ^ (a >> 11); + return a; +} + +export function hash2(i: number, j: number) { + let a = 23; + a = (31 * a + i) | 0; + a = (31 * a + j) | 0; + a = a ^ (a >> 4) + a = (a ^ 0xdeadbeef) + (a << 5); + a = a ^ (a >> 11); + return a; +} + +export function hash3(i: number, j: number, k: number) { + let a = 23; + a = (31 * a + i) | 0; + a = (31 * a + j) | 0; + a = (31 * a + k) | 0; + a = a ^ (a >> 4) + a = (a ^ 0xdeadbeef) + (a << 5); + a = a ^ (a >> 11); + return a; +} + +export function hash4(i: number, j: number, k: number, l: number) { + let a = 23; + a = (31 * a + i) | 0; + a = (31 * a + j) | 0; + a = (31 * a + k) | 0; + a = (31 * a + l) | 0; + a = a ^ (a >> 4) + a = (a ^ 0xdeadbeef) + (a << 5); + a = a ^ (a >> 11); + return a; +} \ No newline at end of file diff --git a/src/structure/collections/int-pair.ts b/src/structure/collections/int-pair.ts index 2f6615307..e48e5cc76 100644 --- a/src/structure/collections/int-pair.ts +++ b/src/structure/collections/int-pair.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Iterator from './iterator' +import { hash2 } from './hash-functions' interface IntPair { fst: number, snd: number } @@ -75,6 +75,11 @@ namespace IntPair { if (x !== 0) return x; return _int32[1] - _int32_1[1]; } + + export function packedHashCode(packed: number) { + _float64[0] = packed; + return hash2(_int32[0], _int32[1]); + } } export default IntPair \ No newline at end of file diff --git a/src/structure/collections/multi-set.ts b/src/structure/collections/multi-set.ts index 91ce6174a..bdebe3f74 100644 --- a/src/structure/collections/multi-set.ts +++ b/src/structure/collections/multi-set.ts @@ -8,12 +8,20 @@ import OrderedSet from './ordered-set' import Iterator from './iterator' import IntPair from './int-pair' import { sortArray } from './sort' +import { hash1 } from './hash-functions' -type MultiSetElements = { [id: number]: OrderedSet, keys: OrderedSet } +type MultiSetElements = { [id: number]: OrderedSet, size: number, hashCode: number, keys: OrderedSet } type MultiSet = number | MultiSetElements namespace MultiSet { - export const Empty: MultiSet = { keys: OrderedSet.Empty }; + export const Empty: MultiSet = { size: 0, hashCode: 0, keys: OrderedSet.Empty }; + + export function create(data: number | ArrayLike<number> | IntPair | { [id: number]: OrderedSet }): MultiSet { + if (typeof data === 'number') return data; + if (IntPair.is(data)) return IntPair.pack(data); + if (isArrayLike(data)) return ofPackedPairs(data); + return ofObject(data); + } export function keys(set: MultiSet): OrderedSet { if (typeof set === 'number') return OrderedSet.ofSingleton(set); @@ -31,25 +39,61 @@ namespace MultiSet { return set[key]; } - function isArrayLike(x: any): x is ArrayLike<number> { - return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer)); + export function size(set: MultiSet) { + if (typeof set === 'number') return 0; + return set.size; } - export function create(data: number | ArrayLike<number> | IntPair | { [id: number]: OrderedSet }): MultiSet { - if (typeof data === 'number') return data; - if (IntPair.is(data)) return IntPair.pack(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; + export function hashCode(set: MultiSet) { + if (typeof set === 'number') return IntPair.packedHashCode(set); + if (set.hashCode !== -1) return set.hashCode; + return computeHash(set); + } + + export function areEqual(a: MultiSet, b: MultiSet): boolean { + if (a === b) return true; + if (typeof a === 'number') { + if (typeof b === 'number') return a === b; + return false; + } + if (typeof b === 'number') return false; + return areEqualEE(a, b); + } + + export function areIntersecting(a: MultiSet, b: MultiSet): boolean { + if (a === b) return true; + if (typeof a === 'number') { + if (typeof b === 'number') return a === b; + return areIntersectingNE(a, b); + } + if (typeof b === 'number') return areIntersectingNE(b, a); + return areIntersectingEE(a, b); + } + + export function intersect(a: MultiSet, b: MultiSet): MultiSet { + if (a === b) return a; + if (typeof a === 'number') { + if (typeof b === 'number') return a === b ? a : Empty; + return intersectNE(a, b); + } + if (typeof b === 'number') return intersectNE(b, a); + return intersectEE(a, b); + } + + export function subtract(a: MultiSet, b: MultiSet): MultiSet { + if (a === b) return Empty; + if (typeof a === 'number') { + if (typeof b === 'number') return a === b ? Empty : a; + return subtractNE(a, b); } - 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; + if (typeof b === 'number') return subtractEN(a, b); + return subtractEE(a, b); + } + + // TODO: union + + export function union(sets: ArrayLike<MultiSet>): MultiSet { + return 0 as any; } class ElementsIterator implements Iterator<IntPair> { @@ -97,30 +141,202 @@ namespace MultiSet { if (typeof set === 'number') return Iterator.Value(IntPair.unpack1(set)); return new ElementsIterator(set); } +} - function ofPackedPairs(xs: ArrayLike<number>): MultiSet { - if (xs.length === 0) return Empty; - const sets: { [key: number]: number[] } = Object.create(null); - const p = IntPair.zero(); - for (let i = 0, _i = xs.length; i < _i; i++) { - IntPair.unpack(xs[i], p); - const set = sets[p.fst]; - if (set) set[set.length] = p.snd; - else sets[p.fst] = [p.snd]; - } - const ret: { [key: number]: OrderedSet } = Object.create(null); - for (const _k of Object.keys(sets)) { - const k = +_k; - ret[k] = OrderedSet.ofSortedArray(sortArray(sets[k])); +const pair = IntPair.zero(); + + +function isArrayLike(x: any): x is ArrayLike<number> { + return x && (typeof x.length === 'number' && (x instanceof Array || !!x.buffer)); +} + +function ofObject(data: { [id: number]: OrderedSet }) { + 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 MultiSet.Empty; + if (keys.length === 1) { + const set = data[keys[0]]; + if (set.size === 1) return IntPair.pack1(keys[0], set.elementAt(0)); + } + return ofObject1(keys, data); +} + +function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { + if (keys.length === 1) { + const k = keys[0]; + const set = data[k]; + if (set.size === 1) return IntPair.pack1(k, set.elementAt(0)); + } + sortArray(keys); + return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data); +} + +function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { + if (keys.size === 1) { + const k = keys.elementAt(0); + const set = data[k]; + if (set.size === 1) return IntPair.pack1(k, set.elementAt(0)); + } + return _createObjectOrdered(keys, data); +} + +function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { + const ret: MultiSetElements = Object.create(null); + ret.keys = keys; + let size = 0; + for (let i = 0, _i = keys.size; i < _i; i++) { + const k = keys.elementAt(i); + const set = data[k]; + ret[k] = set; + size += set.size; + } + ret.size = size; + ret.hashCode = -1; + return ret; +} + +function ofPackedPairs(xs: ArrayLike<number>): MultiSet { + if (xs.length === 0) return MultiSet.Empty; + const sets: { [key: number]: number[] } = Object.create(null); + const p = IntPair.zero(); + for (let i = 0, _i = xs.length; i < _i; i++) { + IntPair.unpack(xs[i], p); + const set = sets[p.fst]; + if (set) set[set.length] = p.snd; + else sets[p.fst] = [p.snd]; + } + const ret: { [key: number]: OrderedSet } = Object.create(null); + const keys = []; + for (const _k of Object.keys(sets)) { + const k = +_k; + keys[keys.length] = k; + ret[k] = OrderedSet.ofSortedArray(sortArray(sets[k])); + } + return ofObject1(keys, ret); +} + +function computeHash(set: MultiSetElements) { + const { keys } = set; + let hash = 23; + for (let i = 0, _i = keys.size; i < _i; i++) { + const k = keys.elementAt(i); + hash = (31 * hash + k) | 0; + hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0; + } + hash = (31 * hash + set.size) | 0; + hash = hash1(hash); + set.hashCode = hash; + return hash; +} + +function areEqualEE(a: MultiSetElements, b: MultiSetElements) { + if (a === b) return true; + if (a.size !== b.size) return false; + + const keys = a.keys; + if (!OrderedSet.areEqual(keys, b.keys)) return false; + for (let i = 0, _i = keys.size; i < _i; i++) { + const k = keys.elementAt(i); + if (!OrderedSet.areEqual(a[k], b[k])) return false; + } + return true; +} + +function areIntersectingNE(a: number, b: MultiSetElements) { + IntPair.unpack(a, pair); + return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd); +} + +function areIntersectingEE(a: MultiSetElements, b: MultiSetElements) { + if (a === b) return true; + const keysA = a.keys, keysB = b.keys; + if (!OrderedSet.areIntersecting(a.keys, b.keys)) return false; + const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + for (let i = start; i < end; i++) { + const k = keysA.elementAt(i); + if (keysB.has(k) && OrderedSet.areIntersecting(a[k], b[k])) return true; + } + return false; +} + +function intersectNE(a: number, b: MultiSetElements) { + IntPair.unpack(a, pair); + return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd) ? a : MultiSet.Empty; +} + +function intersectEE(a: MultiSetElements, b: MultiSetElements) { + if (a === b) return a; + + const keysA = a.keys, keysB = b.keys; + if (!OrderedSet.areIntersecting(a.keys, b.keys)) return MultiSet.Empty; + const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + + const keys = [], ret = Object.create(null); + for (let i = start; i < end; i++) { + const k = keysA.elementAt(i); + if (keysB.has(k)) { + const intersection = OrderedSet.intersect(a[k], b[k]); + if (intersection.size > 0) { + keys[keys.length] = k; + ret[k] = intersection; + } } - return create(ret); } + return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); +} - // TODO: size, ofObject, hashCode (and cache it), equal, union, intersection, subtraction +function subtractNE(a: number, b: MultiSetElements) { + IntPair.unpack(a, pair); + return b.keys.has(pair.fst) && b[pair.fst].has(pair.snd) ? MultiSet.Empty : a; +} - export function union(sets: ArrayLike<MultiSet>): MultiSet { - return 0 as any; +function subtractEN(a: MultiSetElements, b: number): MultiSet { + const aKeys = a.keys; + IntPair.unpack(b, pair); + if (!aKeys.has(pair.fst) || !a[pair.fst].has(pair.snd)) return a; + const set = a[pair.fst]; + if (set.size === 1) { + return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(pair.fst)), a); + } else { + return { ...a, [pair.fst]: OrderedSet.subtract(set, OrderedSet.ofSingleton(pair.snd)), size: a.size - 1, hashCode: -1 } + } +} + +function subtractEE(a: MultiSetElements, b: MultiSetElements) { + if (a === b) return a; + + const keysA = a.keys, keysB = b.keys; + if (!OrderedSet.areIntersecting(a.keys, b.keys)) return MultiSet.Empty; + const { start, end } = OrderedSet.getIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + + const keys = [], ret = Object.create(null); + for (let i = 0; i < start; i++) { + const k = keysA.elementAt(i); + keys[keys.length] = k; + ret[k] = a[k]; + } + for (let i = start; i < end; i++) { + const k = keysA.elementAt(i); + if (keysB.has(k)) { + const subtraction = OrderedSet.subtract(a[k], b[k]); + if (subtraction.size > 0) { + keys[keys.length] = k; + ret[k] = subtraction; + } + } else { + keys[keys.length] = k; + ret[k] = a[k]; + } + } + for (let i = end, _i = keysA.size; i < _i; i++) { + const k = keysA.elementAt(i); + keys[keys.length] = k; + ret[k] = a[k]; } + return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); } export default MultiSet \ No newline at end of file diff --git a/src/structure/collections/ordered-set.ts b/src/structure/collections/ordered-set.ts index 96dacd506..a63f210ac 100644 --- a/src/structure/collections/ordered-set.ts +++ b/src/structure/collections/ordered-set.ts @@ -5,6 +5,7 @@ */ import Iterator from './iterator' +import { hash3, hash4 } from './hash-functions' /** An immutable ordered set. */ interface OrderedSet { @@ -58,20 +59,18 @@ namespace OrderedSet { 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 = new RangeImpl(0, -1); + export const Empty: OrderedSet = new RangeImpl(0, -1); export function isEmpty(a: OrderedSet) { return a.size === 0; } + export function min(a: OrderedSet) { return (a as Impl).min; } + export function max(a: OrderedSet) { return (a as Impl).max; } export function hashCode(a: OrderedSet) { // hash of tuple (size, min, max, mid) const { size } = a; - let hash = 23; - if (!size) return hash; - hash = 31 * hash + size; - hash = 31 * hash + a.elementAt(0); - hash = 31 * hash + a.elementAt(size - 1); - if (size > 2) hash = 31 * hash + a.elementAt(size >> 1); - return hash; + if (!size) return 0; + if (size > 2) return hash4(size, a.elementAt(0), a.elementAt(size - 1), a.elementAt(size >> 0)); + return hash3(size, a.elementAt(0), a.elementAt(size - 1)); } // TODO: possibly add more hash functions to allow for multilevel hashing. @@ -103,7 +102,15 @@ namespace OrderedSet { return isSubsetAA((a as ArrayImpl).values, (toTest as ArrayImpl).values); } + export function getIntervalRange(a: OrderedSet, min: number, max: number) { + const { start, end } = a instanceof RangeImpl + ? getStartEndR(a, min, max) + : getStartEndA((a as ArrayImpl).values, min, max); + return { start, end }; + } + export function union(a: OrderedSet, b: OrderedSet) { + if (a === b) return a; if (a instanceof RangeImpl) { if (b instanceof RangeImpl) return unionRR(a, b); return unionAR(b as ArrayImpl, a); @@ -113,6 +120,7 @@ namespace OrderedSet { } export function intersect(a: OrderedSet, b: OrderedSet) { + if (a === b) return a; if (a instanceof RangeImpl) { if (b instanceof RangeImpl) return intersectRR(a, b); return intersectAR(b as ArrayImpl, a); @@ -125,6 +133,7 @@ namespace OrderedSet { } export function subtract(a: OrderedSet, b: OrderedSet) { + if (a === b) return Empty; if (!areRangesIntersecting(a, b)) return a; if (a instanceof RangeImpl) { @@ -184,7 +193,19 @@ function getMaxIntersectionRange(xs: ArrayLike<number>, ys: ArrayLike<number>) { } const _startEndRet = { start: 0, end: 0 }; -function getStartEnd(xs: ArrayLike<number>, min: number, max: number) { + +function getStartEndR(r: RangeImpl, min: number, max: number) { + if (max < min) { + _startEndRet.start = 0; + _startEndRet.end = 0; + return _startEndRet; + } + _startEndRet.start = min <= r.max ? Math.max(r.min, min) - r.min : r.size; + _startEndRet.end = max >= r.min ? Math.min(r.max, max) - r.min + 1 : r.size; + return _startEndRet; +} + +function getStartEndA(xs: ArrayLike<number>, min: number, max: number) { _startEndRet.start = binarySearchIndex(xs, min); let end = binarySearchIndex(xs, max); if (xs[end] === max) end++; @@ -263,7 +284,7 @@ function unionAR(a: ArrayImpl, b: RangeImpl) { const xs = a.values; const { min, max } = b; - const { start, end } = getStartEnd(xs, min, max); + const { start, end } = getStartEndA(xs, min, max); const size = start + (xs.length - end) + b.size; const indices = new Int32Array(size); @@ -332,7 +353,7 @@ function intersectAR(a: ArrayImpl, r: RangeImpl) { if (!r.size) return OrderedSet.Empty; const xs = a.values; - const { start, end } = getStartEnd(xs, r.min, r.max); + const { start, end } = getStartEndA(xs, r.min, r.max); const resultSize = end - start; if (!resultSize) return OrderedSet.Empty; @@ -395,7 +416,7 @@ function subtractAR(a: ArrayImpl, r: RangeImpl) { const xs = a.values; const { min, max } = r; - const { start, end } = getStartEnd(xs, min, max); + const { start, end } = getStartEndA(xs, min, max); const size = xs.length - (end - start); if (size <= 0) return OrderedSet.Empty; const ret = new Int32Array(size); @@ -409,7 +430,7 @@ function subtractRA(r: RangeImpl, ys: ArrayLike<number>) { if (!r.size) return r; const { min, max } = r; - const { start, end } = getStartEnd(ys, min, max); + const { start, end } = getStartEndA(ys, min, max); const commonCount = end - start; const resultSize = r.size - commonCount; if (resultSize <= 0) return OrderedSet.Empty; diff --git a/src/structure/spec/collections.spec.ts b/src/structure/spec/collections.spec.ts index 709eedef8..4bf0092ba 100644 --- a/src/structure/spec/collections.spec.ts +++ b/src/structure/spec/collections.spec.ts @@ -201,6 +201,21 @@ describe('range set', () => { expect(arr136.indexOf(11)).toBe(-1); }); + it('interval range', () => { + expect(OrderedSet.getIntervalRange(empty, 9, 11)).toEqual({ start: 0, end: 0 }); + expect(OrderedSet.getIntervalRange(empty, -9, -6)).toEqual({ start: 0, end: 0 }); + expect(OrderedSet.getIntervalRange(singleton10, 9, 11)).toEqual({ start: 0, end: 1 }); + expect(OrderedSet.getIntervalRange(range1_4, 2, 3)).toEqual({ start: 1, end: 3 }); + expect(OrderedSet.getIntervalRange(range1_4, -10, 2)).toEqual({ start: 0, end: 2 }); + expect(OrderedSet.getIntervalRange(range1_4, -10, 20)).toEqual({ start: 0, end: 4 }); + expect(OrderedSet.getIntervalRange(range1_4, 3, 20)).toEqual({ start: 2, end: 4 }); + expect(OrderedSet.getIntervalRange(arr136, 0, 1)).toEqual({ start: 0, end: 1 }); + expect(OrderedSet.getIntervalRange(arr136, 0, 3)).toEqual({ start: 0, end: 2 }); + expect(OrderedSet.getIntervalRange(arr136, 0, 4)).toEqual({ start: 0, end: 2 }); + expect(OrderedSet.getIntervalRange(arr136, 2, 4)).toEqual({ start: 1, end: 2 }); + expect(OrderedSet.getIntervalRange(arr136, 2, 7)).toEqual({ start: 1, end: 3 }); + }) + testEq('union ES', OrderedSet.union(empty, singleton10), [10]); testEq('union ER', OrderedSet.union(empty, range1_4), [1, 2, 3, 4]); testEq('union EA', OrderedSet.union(empty, arr136), [1, 3, 6]); @@ -298,20 +313,21 @@ describe('multiset', () => { const p = (i: number, j: number) => IntPair.create(i, j); const r = (i: number, j: number) => IntPair.pack1(i, j); - function iteratorPairsToArray(it: Iterator<IntPair>): IntPair[] { + function setToPairs(set: MultiSet): IntPair[] { const ret = []; + const it = MultiSet.values(set); 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)]); + expect(setToPairs(set)).toEqual([p(10, 11)]); }); it('singleton number', () => { const set = MultiSet.create(r(10, 11)); - expect(iteratorPairsToArray(MultiSet.values(set))).toEqual([p(10, 11)]); + expect(setToPairs(set)).toEqual([p(10, 11)]); }); it('multi', () => { @@ -319,12 +335,73 @@ describe('multiset', () => { 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)]); + expect(setToPairs(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)]); + expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]); + }); + + it('equality', () => { + const a = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const b = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const c = MultiSet.create([r(1, 3), r(0, 4), r(0, 6), r(0, 2)]); + const d = MultiSet.create([r(1, 3)]); + const e = MultiSet.create([r(1, 3)]); + const f = MultiSet.create([r(3, 3)]); + + expect(MultiSet.areEqual(a, a)).toBe(true); + expect(MultiSet.areEqual(a, b)).toBe(true); + expect(MultiSet.areEqual(a, c)).toBe(false); + expect(MultiSet.areEqual(a, d)).toBe(false); + expect(MultiSet.areEqual(d, d)).toBe(true); + expect(MultiSet.areEqual(d, e)).toBe(true); + expect(MultiSet.areEqual(d, f)).toBe(false); }); + it('are intersecting', () => { + const a = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const b = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const c = MultiSet.create([r(1, 3), r(0, 4), r(0, 6), r(0, 2)]); + const d = MultiSet.create([r(1, 3)]); + const e = MultiSet.create([r(1, 3)]); + const f = MultiSet.create([r(3, 3)]); + const g = MultiSet.create([r(10, 3), r(8, 1), r(7, 6), r(3, 2)]); + + expect(MultiSet.areIntersecting(a, a)).toBe(true); + expect(MultiSet.areIntersecting(a, b)).toBe(true); + expect(MultiSet.areIntersecting(a, c)).toBe(true); + expect(MultiSet.areIntersecting(a, d)).toBe(true); + expect(MultiSet.areIntersecting(a, g)).toBe(false); + expect(MultiSet.areIntersecting(d, d)).toBe(true); + expect(MultiSet.areIntersecting(d, e)).toBe(true); + expect(MultiSet.areIntersecting(d, f)).toBe(false); + }); + + it('intersection', () => { + const a = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const b = MultiSet.create([r(10, 3), r(0, 1), r(0, 6), r(4, 2)]); + const c = MultiSet.create([r(1, 3)]); + const d = MultiSet.create([r(2, 3)]); + expect(MultiSet.intersect(a, a)).toBe(a); + expect(setToPairs(MultiSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]); + expect(setToPairs(MultiSet.intersect(a, c))).toEqual([p(1, 3)]); + expect(setToPairs(MultiSet.intersect(c, d))).toEqual([]); + }); + + it('subtract', () => { + const a = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const a1 = MultiSet.create([r(1, 3), r(0, 1), r(0, 6), r(0, 2)]); + const b = MultiSet.create([r(10, 3), r(0, 1), r(0, 6), r(4, 2)]); + const c = MultiSet.create([r(1, 3)]); + const d = MultiSet.create([r(2, 3)]); + expect(setToPairs(MultiSet.subtract(a, a))).toEqual([]); + expect(setToPairs(MultiSet.subtract(a, a1))).toEqual([]); + expect(setToPairs(MultiSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]); + expect(setToPairs(MultiSet.subtract(c, d))).toEqual([p(1, 3)]); + expect(setToPairs(MultiSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]); + expect(setToPairs(MultiSet.subtract(c, a))).toEqual([]); + expect(setToPairs(MultiSet.subtract(d, a))).toEqual([p(2, 3)]); + }); }); \ No newline at end of file -- GitLab