@rimbu/multiset
Version:
An immutable Set where each element can occur multiple times
506 lines • 15.7 kB
JavaScript
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