-
David Sehnal authoredDavid Sehnal authored
multi-set.ts 17.62 KiB
/**
* 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 IntTuple from './int-tuple'
import { sortArray } from './sort'
import { hash1 } from './hash-functions'
/** A map-like representation of integer set */
interface MultiSet { /*'@type': 'int-multi-set'*/ }
namespace MultiSet {
export const Empty: MultiSet = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty } as MultiSetElements as any;
export function create(data: IntTuple | ArrayLike<IntTuple> | IntTuple | { [id: number]: OrderedSet }): MultiSet {
if (typeof data === 'number') return data;
if (IntTuple.is(data)) return IntTuple.pack1(data) as number;
if (isArrayLike(data)) return ofTuples(data);
return ofObject(data as { [id: number]: OrderedSet });
}
export function keys(set: MultiSet): OrderedSet {
if (typeof set === 'number') return OrderedSet.ofSingleton(set);
return (set as MultiSetElements).keys;
}
export function keyCount(set: MultiSet): number {
if (typeof set === 'number') return 1;
return OrderedSet.size((set as MultiSetElements).keys);
}
export function hasKey(set: MultiSet, key: number): boolean {
if (typeof set === 'number') return IntTuple.fst(set) === key;
return OrderedSet.has((set as MultiSetElements).keys, key);
}
export function getKey(set: MultiSet, index: number): number {
if (typeof set === 'number') return IntTuple.fst(set);
return OrderedSet.getAt((set as MultiSetElements).keys, index);
}
export function has(set: MultiSet, t: IntTuple): boolean {
if (typeof set === 'number') return IntTuple.areEqual(t, set);
IntTuple.unpack(t, _hasP);
return OrderedSet.has((set as MultiSetElements).keys, _hasP.fst) ? OrderedSet.has((set as MultiSetElements)[_hasP.fst], _hasP.snd) : false;
}
const _hasP = IntTuple.zero();
export function getByKey(set: MultiSet, key: number): OrderedSet {
if (typeof set === 'number') {
IntTuple.unpack(set, _gS);
return _gS.fst === key ? OrderedSet.ofSingleton(_gS.snd) : OrderedSet.Empty;
}
return OrderedSet.has((set as MultiSetElements).keys, key) ? (set as MultiSetElements)[key] : OrderedSet.Empty;
}
const _gS = IntTuple.zero();
export function getByIndex(set: MultiSet, index: number): OrderedSet {
if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(IntTuple.snd(set)) : OrderedSet.Empty;
const key = OrderedSet.getAt((set as MultiSetElements).keys, index);
return (set as MultiSetElements)[key] || OrderedSet.Empty;
}
export function getAt(set: MultiSet, i: number): IntTuple {
if (typeof set === 'number') return set;
return getAtE(set as MultiSetElements, i);
}
export function indexOf(set: MultiSet, t: IntTuple) {
if (typeof set === 'number') return IntTuple.areEqual(set, t) ? 0 : -1;
return indexOfE(set as MultiSetElements, t);
}
/** Number elements in the "child" sets */
export function size(set: MultiSet) {
if (typeof set === 'number') return 1;
return (set as MultiSetElements).offsets[(set as MultiSetElements).offsets.length - 1];
}
export function hashCode(set: MultiSet) {
if (typeof set === 'number') return IntTuple.hashCode(set);
if ((set as MultiSetElements).hashCode !== -1) return (set as MultiSetElements).hashCode;
return computeHash((set as MultiSetElements));
}
export function areEqual(a: MultiSet, b: MultiSet): boolean {
if (typeof a === 'number') {
if (typeof b === 'number') return IntTuple.areEqual(a, b);
return false;
}
if (typeof b === 'number') return false;
return areEqualEE(a as MultiSetElements, b as MultiSetElements);
}
export function areIntersecting(a: MultiSet, b: MultiSet): boolean {
if (typeof a === 'number') {
if (typeof b === 'number') return IntTuple.areEqual(a, b);
return areIntersectingNE(a, b as MultiSetElements);
}
if (typeof b === 'number') return areIntersectingNE(b, a as MultiSetElements);
return areIntersectingEE(a as MultiSetElements, b as MultiSetElements);
}
export function intersect(a: MultiSet, b: MultiSet): MultiSet {
if (typeof a === 'number') {
if (typeof b === 'number') return IntTuple.areEqual(a, b) ? a : Empty;
return intersectNE(a, b as MultiSetElements);
}
if (typeof b === 'number') return intersectNE(b, a as MultiSetElements);
return intersectEE(a as MultiSetElements, b as MultiSetElements);
}
export function subtract(a: MultiSet, b: MultiSet): MultiSet {
if (typeof a === 'number') {
if (typeof b === 'number') return IntTuple.areEqual(a, b) ? Empty : a;
return subtractNE(a, b as MultiSetElements);
}
if (typeof b === 'number') return subtractEN(a as MultiSetElements, b);
return subtractEE(a as MultiSetElements, b as MultiSetElements);
}
export function union(a: MultiSet, b: MultiSet): MultiSet {
return findUnion([a, b]);
}
export function unionMany(sets: ArrayLike<MultiSet>): MultiSet {
return findUnion(sets);
}
class ElementsIterator implements Iterator<IntTuple.Unpacked> {
private pair = IntTuple.zero();
private keyCount: number;
private setIndex = -1;
private currentIndex = 0;
private currentSize = 0;
private currentSet: OrderedSet = OrderedSet.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;
if (this.currentIndex >= this.currentSize) {
if (!this.advance()) return this.pair;
}
this.pair.snd = OrderedSet.getAt(this.currentSet, this.currentIndex++);
return this.pair;
}
private advance() {
if (++this.setIndex >= this.keyCount) {
this.done = true;
return false;
}
const unit = OrderedSet.getAt(this.elements.keys, this.setIndex);
this.pair.fst = unit;
this.currentSet = this.elements[unit];
this.currentIndex = 0;
this.currentSize = OrderedSet.size(this.currentSet);
return true;
}
constructor(private elements: MultiSetElements) {
this.keyCount = OrderedSet.size(elements.keys);
this.done = this.keyCount === 0;
this.advance();
}
}
export function values(set: MultiSet): Iterator<IntTuple.Unpacked> {
if (typeof set === 'number') return Iterator.Value(IntTuple.unpack1(set));
return new ElementsIterator(set as MultiSetElements);
}
}
interface MultiSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet }
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 (OrderedSet.size(data[k]) > 0) keys[keys.length] = k;
}
if (!keys.length) return MultiSet.Empty;
if (keys.length === 1) {
const set = data[keys[0]];
if (OrderedSet.size(set) === 1) return IntTuple.pack(keys[0], OrderedSet.getAt(set, 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 (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
}
sortArray(keys);
return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data);
}
function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
if (OrderedSet.size(keys) === 1) {
const k = OrderedSet.getAt(keys, 0);
const set = data[k];
if (OrderedSet.size(set) === 1) return IntTuple.pack(k, OrderedSet.getAt(set, 0));
}
return _createObjectOrdered(keys, data);
}
function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) {
const ret: MultiSetElements = Object.create(null);
ret.keys = keys;
const offsets = [0];
let size = 0;
for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
const k = OrderedSet.getAt(keys, i);
const set = data[k];
ret[k] = set;
size += OrderedSet.size(set);
offsets[offsets.length] = size;
}
ret.offsets = offsets;
ret.hashCode = -1;
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 ofTuples(xs: ArrayLike<IntTuple>): MultiSet {
if (xs.length === 0) return MultiSet.Empty;
const sets: { [key: number]: number[] } = Object.create(null);
const p = IntTuple.zero();
for (let i = 0, _i = xs.length; i < _i; i++) {
IntTuple.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(normalizeArray(sets[k]));
}
return ofObject1(keys, ret);
}
function getOffsetIndex(xs: ArrayLike<number>, value: number) {
let min = 0, max = xs.length - 1;
while (min < max) {
const mid = (min + max) >> 1;
const v = xs[mid];
if (value < v) max = mid - 1;
else if (value > v) min = mid + 1;
else return mid;
}
if (min > max) {
return max;
}
return value < xs[min] ? min - 1 : min;
}
function getAtE(set: MultiSetElements, i: number) {
const { offsets, keys } = set;
const o = getOffsetIndex(offsets, i);
if (o >= offsets.length - 1) return 0;
const k = OrderedSet.getAt(keys, o);
const e = OrderedSet.getAt(set[k], i - offsets[o]);
return IntTuple.pack(k, e);
}
const _iOE = IntTuple.zero();
function indexOfE(set: MultiSetElements, t: IntTuple) {
IntTuple.unpack(t, _iOE);
const { keys } = set;
const setIdx = OrderedSet.indexOf(keys, _iOE.fst);
if (setIdx < 0) return -1;
const o = OrderedSet.indexOf(set[_iOE.fst], _iOE.snd);
if (o < 0) return -1;
return set.offsets[setIdx] + o;
}
function computeHash(set: MultiSetElements) {
const { keys } = set;
let hash = 23;
for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
const k = OrderedSet.getAt(keys, i);
hash = (31 * hash + k) | 0;
hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0;
}
hash = (31 * hash + MultiSet.size(set)) | 0;
hash = hash1(hash);
set.hashCode = hash;
return hash;
}
function areEqualEE(a: MultiSetElements, b: MultiSetElements) {
if (a === b) return true;
if (MultiSet.size(a) !== MultiSet.size(a)) return false;
const keys = a.keys;
if (!OrderedSet.areEqual(keys, b.keys)) return false;
for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
const k = OrderedSet.getAt(keys, i);
if (!OrderedSet.areEqual(a[k], b[k])) return false;
}
return true;
}
const _aeP = IntTuple.zero();
function areIntersectingNE(a: IntTuple, b: MultiSetElements) {
IntTuple.unpack(a, _aeP);
return OrderedSet.has(b.keys, _aeP.fst) && OrderedSet.has(b[_aeP.fst], _aeP.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 = OrderedSet.getAt(keysA, i);
if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true;
}
return false;
}
const _nP = IntTuple.zero();
function intersectNE(a: IntTuple, b: MultiSetElements) {
IntTuple.unpack(a, _nP);
return OrderedSet.has(b.keys, _nP.fst) && OrderedSet.has(b[_nP.fst], _nP.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 = OrderedSet.getAt(keysA, i);
if (OrderedSet.has(keysB, k)) {
const intersection = OrderedSet.intersect(a[k], b[k]);
if (OrderedSet.size(intersection) > 0) {
keys[keys.length] = k;
ret[k] = intersection;
}
}
}
return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret);
}
const _sNE = IntTuple.zero();
function subtractNE(a: IntTuple, b: MultiSetElements) {
IntTuple.unpack(a, _sNE);
return OrderedSet.has(b.keys, _sNE.fst) && OrderedSet.has(b[_sNE.fst], _sNE.snd) ? MultiSet.Empty : a;
}
const _sEN = IntTuple.zero();
function subtractEN(a: MultiSetElements, b: IntTuple): MultiSet {
const aKeys = a.keys;
IntTuple.unpack(b, _sEN);
if (!OrderedSet.has(aKeys, _sEN.fst) || !OrderedSet.has(a[_sEN.fst], _sEN.snd)) return a;
const set = a[_sEN.fst];
if (OrderedSet.size(set) === 1) {
return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(_sEN.fst)), a);
} else {
return ofObjectOrdered(OrderedSet.subtract(set, OrderedSet.ofSingleton(_sEN.snd)), a);
}
}
function subtractEE(a: MultiSetElements, b: MultiSetElements) {
if (a === b) return MultiSet.Empty;
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 = OrderedSet.getAt(keysA, i);
keys[keys.length] = k;
ret[k] = a[k];
}
for (let i = start; i < end; i++) {
const k = OrderedSet.getAt(keysA, i);
if (OrderedSet.has(keysB, k)) {
const subtraction = OrderedSet.subtract(a[k], b[k]);
if (OrderedSet.size(subtraction) > 0) {
keys[keys.length] = k;
ret[k] = subtraction;
}
} else {
keys[keys.length] = k;
ret[k] = a[k];
}
}
for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) {
const k = OrderedSet.getAt(keysA, i);
keys[keys.length] = k;
ret[k] = a[k];
}
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 && MultiSet.areEqual(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 as MultiSetElements);
}
if (MultiSet.size(ns) > 0) {
if (typeof ns === 'number') unionIntoN(ret, ns);
else unionInto(ret, ns as MultiSetElements);
}
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 ofTuples(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 ofTuples(packed);
}
function unionInto(data: { [key: number]: OrderedSet }, a: MultiSetElements) {
const keys = a.keys;
for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) {
const k = OrderedSet.getAt(keys, i);
const set = data[k];
if (set) data[k] = OrderedSet.union(set, a[k]);
else data[k] = a[k];
}
}
const _uIN = IntTuple.zero();
function unionIntoN(data: { [key: number]: OrderedSet }, a: IntTuple) {
IntTuple.unpack(a, _uIN);
const set = data[_uIN.fst];
if (set) {
data[_uIN.fst] = OrderedSet.union(set, OrderedSet.ofSingleton(_uIN.snd));
} else {
data[_uIN.fst] = OrderedSet.ofSingleton(_uIN.snd);
}
}
export default MultiSet