UNPKG

@rimbu/multiset

Version:

An immutable Set where each element can occur multiple times

506 lines 15.7 kB
import { Arr, RimbuError } from '@rimbu/base'; import { EmptyBase, NonEmptyBase, } from '@rimbu/collection-types/map-custom'; import { Reducer, Stream, } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; import { TraverseState, } from '@rimbu/common'; export class MultiSetEmpty extends EmptyBase { constructor(context) { super(); this.context = context; } add(elem, amount) { if (undefined !== amount && amount <= 0) return this; const addAmount = amount ?? 1; const countMap = this.context.countMapContext.of([ elem, addAmount, ]); return this.context.createNonEmpty(countMap, addAmount); } get countMap() { return this.context.countMapContext.empty(); } get sizeDistinct() { return 0; } streamDistinct() { return Stream.empty(); } addAll(values) { return this.context.from(values); } addEntries(entries) { if (isEmptyStreamSourceInstance(entries)) return this; const builder = this.toBuilder(); builder.addEntries(entries); return builder.build(); } remove() { return this; } removeAllSingle() { return this; } removeAllEvery() { return this; } setCount(elem, amount) { return this.add(elem, amount); } modifyCount(value, update) { return this.add(value, update(0)); } has() { return false; } count() { return 0; } forEach() { // } filterEntries() { return this; } toBuilder() { return this.context.builder(); } toArray() { return []; } toString() { return `${this.context.typeTag}()`; } toJSON() { return { dataType: this.context.typeTag, value: [], }; } } export class MultiSetNonEmpty extends NonEmptyBase { constructor(context, countMap, size) { super(); this.context = context; this.countMap = countMap; this.size = size; } assumeNonEmpty() { return this; } copy(countMap, size) { if (countMap === this.countMap) return this; return this.context.createNonEmpty(countMap, size); } copyE(countMap, size) { if (countMap.nonEmpty()) return this.copy(countMap, size); return this.context.empty(); } get sizeDistinct() { return this.countMap.size; } stream() { return this.countMap .stream() .flatMap(([value, count]) => Stream.of(value).repeat(count)); } streamDistinct() { return this.countMap.streamKeys(); } has(elem) { return this.countMap.hasKey(elem); } count(elem) { return this.countMap.get(elem, 0); } add(elem, amount = 1) { if (amount <= 0) return this; return this.copy(this.countMap .modifyAt(elem, { ifNew: amount, ifExists: (count) => count + amount, }) .assumeNonEmpty(), this.size + amount); } addAll(values) { if (isEmptyStreamSourceInstance(values)) return this; const builder = this.toBuilder(); builder.addAll(values); return builder.build().assumeNonEmpty(); } addEntries(entries) { if (isEmptyStreamSourceInstance(entries)) return this; const builder = this.toBuilder(); builder.addEntries(entries); return builder.build().assumeNonEmpty(); } setCount(elem, amount) { if (amount <= 0) return this.remove(elem); let sizeDelta = amount; const newCountMap = this.countMap .modifyAt(elem, { ifNew: amount, ifExists: (count) => { sizeDelta -= count; return amount; }, }) .assumeNonEmpty(); return this.copyE(newCountMap, this.size + sizeDelta); } modifyCount(value, update) { let sizeDelta = 0; const newCountMap = this.countMap .modifyAt(value, { ifNew: (none) => { const newAmount = update(0); if (newAmount <= 0) return none; sizeDelta += newAmount; return newAmount; }, ifExists: (amount, remove) => { sizeDelta -= amount; const newAmount = update(amount); if (newAmount <= 0) return remove; sizeDelta += newAmount; return newAmount; }, }) .assumeNonEmpty(); return this.copyE(newCountMap, this.size + sizeDelta); } remove(elem, options = {}) { const { amount = 1 } = options; if (!this.context.isValidElem(elem)) return this; let newSize = this.size; const newCountMap = this.countMap.modifyAt(elem, { ifExists: (count, remove) => { if (amount === 'ALL') { newSize -= count; return remove; } const result = count - amount; if (result <= 0) { newSize -= count; return remove; } newSize -= amount; return result; }, }); return this.copyE(newCountMap, newSize); } removeAllSingle(elems) { if (isEmptyStreamSourceInstance(elems)) return this; const builder = this.toBuilder(); builder.removeAllSingle(elems); return builder.build(); } removeAllEvery(elems) { if (isEmptyStreamSourceInstance(elems)) return this; const builder = this.toBuilder(); builder.removeAllEvery(elems); return builder.build(); } forEach(f, options = {}) { const { reversed = false, state = TraverseState() } = options; if (state.halted) return; const it = this.countMap.stream({ reversed })[Symbol.iterator](); let entry; const { halt } = state; while (!state.halted && undefined !== (entry = it.fastNext())) { const value = entry[0]; let amount = entry[1]; while (!state.halted && --amount >= 0) { f(value, state.nextIndex(), halt); } } } filterEntries(pred, options = {}) { const builder = this.context.builder(); Stream.applyForEach(this.countMap.stream().filter(pred, options), builder.setCount); if (builder.size === this.size) return this; return builder.build(); } toArray() { let result = []; const it = this.countMap[Symbol.iterator](); let entry; while (undefined !== (entry = it.fastNext())) { const amount = entry[1]; if (amount === 1) result.push(entry[0]); else { const newArray = new Array(amount); newArray.fill(entry[0]); result = Arr.concat(result, newArray); } } return result; } toString() { return this.stream().join({ start: `${this.context.typeTag}(`, sep: `, `, end: `)`, }); } toJSON() { return { dataType: this.context.typeTag, value: this.countMap.toArray(), }; } toBuilder() { return new MultiSetBuilder(this.context, this); } } export class MultiSetBuilder { constructor(context, source) { this.context = context; this.source = source; this._lock = 0; this._size = 0; // prettier-ignore this.has = (value) => { return this.source?.has(value) ?? this.countMap.hasKey(value); }; this.add = (value, amount = 1) => { this.checkLock(); if (amount <= 0) return false; this._size += amount; this.countMap.modifyAt(value, { ifNew: amount, ifExists: (count) => count + amount, }); this.source = undefined; return true; }; this.addAll = (source) => { this.checkLock(); return Stream.from(source).filterPure({ pred: this.add }, 1).count() > 0; }; this.addEntries = (entries) => { this.checkLock(); return Stream.applyFilter(entries, { pred: this.add }).count() > 0; }; // prettier-ignore this.remove = (value, amount = 1) => { this.checkLock(); if (typeof amount === 'number' && amount <= 0) return 0; if (!this.context.isValidElem(value)) return 0; let removed = 0; this.countMap.modifyAt(value, { ifExists: (count, remove) => { if (amount === 'ALL') { removed = count; return remove; } const result = count - amount; if (result <= 0) { removed = count; return remove; } removed = amount; return result; }, }); this._size -= removed; if (removed > 0) this.source = undefined; return removed; }; this.setCount = (value, amount) => { this.checkLock(); if (amount <= 0) { return this.remove(value, 'ALL') > 0; } this._size += amount; const changed = this.countMap.modifyAt(value, { ifNew: amount, ifExists: (count) => { this._size -= count; return amount; }, }); if (changed) this.source = undefined; return changed; }; this.modifyCount = (value, update) => { this.checkLock(); const changed = this.countMap.modifyAt(value, { ifNew: (none) => { const newAmount = update(0); if (newAmount <= 0) return none; this._size += newAmount; return newAmount; }, ifExists: (currentCount, remove) => { this._size -= currentCount; const newCount = update(currentCount); if (newCount <= 0) return remove; this._size += newCount; return newCount; }, }); if (changed) this.source = undefined; return changed; }; // prettier-ignore this.count = (value) => { return this.source?.count(value) ?? this.countMap.get(value, 0); }; // prettier-ignore this.removeAll = (values, mode) => { this.checkLock(); if (isEmptyStreamSourceInstance(values)) return false; return (Stream.from(values) .mapPure(this.remove, mode === 'SINGLE' ? 1 : 'ALL') .countElement(0, { negate: true }) > 0); }; // prettier-ignore this.removeAllSingle = (values) => { return this.removeAll(values, 'SINGLE'); }; // prettier-ignore this.removeAllEvery = (values) => { return this.removeAll(values, 'ALL'); }; this.forEach = (f, options = {}) => { const { state = TraverseState() } = options; if (state.halted) return; this._lock++; const { halt } = state; this.countMap.forEach(([value, amount], _, builderHalt) => { let time = 0; while (!state.halted && time++ < amount) { f(value, state.nextIndex(), halt); } if (state.halted) builderHalt(); }); this._lock--; }; this.build = () => { if (undefined !== this.source) return this.source; if (this.isEmpty) return this.context.empty(); const newCountMap = this.countMap .build() .assumeNonEmpty(); return new MultiSetNonEmpty(this.context, newCountMap, this.size); }; if (undefined !== source) this._size = source.size; } get countMap() { if (undefined === this._countMap) { if (undefined === this.source) { this._countMap = this.context.countMapContext.builder(); } else { this._countMap = this.source.countMap.toBuilder(); } } return this._countMap; } checkLock() { if (this._lock) RimbuError.throwModifiedBuilderWhileLoopingOverItError(); } get size() { return this._size; } get sizeDistinct() { return this.source?.sizeDistinct ?? this.countMap.size; } get isEmpty() { return 0 === this.size; } } export class MultiSetContext { constructor(typeTag, countMapContext) { this.typeTag = typeTag; this.countMapContext = countMapContext; this._empty = Object.freeze(new MultiSetEmpty(this)); this.empty = () => { return this._empty; }; this.from = (...sources) => { let builder = this.builder(); let i = -1; const length = sources.length; while (++i < length) { const source = sources[i]; if (isEmptyStreamSourceInstance(source)) continue; if (builder.isEmpty && this.isNonEmptyInstance(source) && source.context === this) { if (i === length - 1) return source; builder = source.toBuilder(); continue; } builder.addAll(source); } return builder.build(); }; // prettier-ignore this.of = (...values) => { return this.from(values); }; this.builder = () => { return new MultiSetBuilder(this); }; this.reducer = (source) => { return Reducer.create(() => undefined === source ? this.builder() : this.from(source).toBuilder(), (builder, value) => { builder.add(value); return builder; }, (builder) => builder.build()); }; } get _types() { return undefined; } isValidElem(elem) { return this.countMapContext.isValidKey(elem); } isNonEmptyInstance(source) { return source instanceof MultiSetNonEmpty; } createNonEmpty(countMap, size) { return new MultiSetNonEmpty(this, countMap, size); } createBuilder(source) { return new MultiSetBuilder(this, source); } } //# sourceMappingURL=base.mjs.map