David Sehnal 7 years ago
parent
commit
8ff4ae7a6e

+ 46 - 0
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;
+}

+ 6 - 1
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

+ 252 - 36
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

+ 34 - 13
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;

+ 82 - 5
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)]);
+    });
 });