@rimbu/sorted
Version:
Immutable SortedMap and SortedSet implementations for TypeScript
253 lines • 9.64 kB
JavaScript
import { Token } from '@rimbu/base';
import { OptLazy, OptLazyOr, Update } from '@rimbu/common';
import { Stream } from '@rimbu/stream';
import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom';
import { SortedBuilder, SortedIndex } from '@rimbu/sorted/common';
export class SortedMapBuilder extends SortedBuilder {
constructor(context, source, _entries, _children, size = source?.size ?? 0) {
super();
this.context = context;
this.source = source;
this._entries = _entries;
this._children = _children;
this.size = size;
this.get = (key, otherwise) => {
if (!this.context.comp.isComparable(key))
return OptLazy(otherwise);
if (undefined !== this.source)
return this.source.get(key, otherwise);
const entryIndex = this.context.findIndex(key, this.entries);
if (entryIndex >= 0) {
return this.entries[entryIndex][1];
}
if (this.hasChildren) {
const childIndex = SortedIndex.next(entryIndex);
const child = this.children[childIndex];
return child.get(key, otherwise);
}
return OptLazy(otherwise);
};
// prettier-ignore
this.hasKey = (key) => {
return Token !== this.get(key, Token);
};
this.addEntry = (entry) => {
this.checkLock();
const result = this.addEntryInternal(entry);
this.normalize();
return result;
};
this.addEntries = (source) => {
this.checkLock();
return Stream.from(source).filterPure({ pred: this.addEntry }).count() > 0;
};
this.set = (key, value) => {
return this.addEntry([key, value]);
};
this.removeKey = (key, otherwise) => {
this.checkLock();
if (!this.context.comp.isComparable(key))
return OptLazy(otherwise);
const result = this.removeInternal(key, otherwise);
this.normalize();
return result;
};
// prettier-ignore
this.removeKeys = (keys) => {
this.checkLock();
if (isEmptyStreamSourceInstance(keys))
return false;
const notFound = Symbol();
return (Stream.from(keys)
.mapPure(this.removeKey, notFound)
.countElement(notFound, { negate: true }) > 0);
};
this.modifyAt = (key, options) => {
this.checkLock();
const result = this.modifyAtInternal(key, options);
this.normalize();
return result;
};
// prettier-ignore
this.updateAt = (key, update, otherwise) => {
let result;
let found = false;
this.modifyAt(key, {
ifExists: (value) => {
result = value;
found = true;
return Update(value, update);
},
});
if (!found)
return OptLazy(otherwise);
return result;
};
this.build = () => {
if (undefined !== this.source)
return this.source;
if (this.size === 0)
return this.context.empty();
if (!this.hasChildren) {
return this.context.leaf(this.entries.slice());
}
return this.context.inner(this.entries.slice(), this.children.map((child) => child.build()), this.size);
};
// prettier-ignore
this.buildMapValues = (f) => {
if (undefined !== this.source)
return this.source.mapValues(f);
if (this.size === 0)
return this.context.empty();
const newEntries = this.entries.map((entry) => [
entry[0],
f(entry[1], entry[0]),
]);
if (!this.hasChildren) {
return this.context.leaf(newEntries);
}
return this.context.inner(newEntries, this.children.map((c) => c.buildMapValues(f)), this.size);
};
}
createNew(source, _entries, _children, size) {
return new SortedMapBuilder(this.context, source, _entries, _children, size);
}
prepareMutate() {
if (undefined === this._entries) {
if (undefined !== this.source) {
if (this.context.isSortedMapEmpty(this.source)) {
this._entries = [];
this._children = [];
}
else if (this.context.isSortedMapLeaf(this.source)) {
this._entries = this.source.entries.slice();
}
else if (this.context.isSortedMapInner(this.source)) {
this._entries = this.source.entries.slice();
this._children = this.source.children.map((child) => this.createNew(child));
}
}
if (undefined === this._entries) {
this._entries = [];
}
}
}
get children() {
this.prepareMutate();
return this._children;
}
set children(value) {
this.prepareMutate();
this.source = undefined;
this._children = value;
}
addEntryInternal(entry) {
const entryIndex = this.context.findIndex(entry[0], this.entries);
if (entryIndex >= 0) {
const currentEntry = this.entries[entryIndex];
if (Object.is(currentEntry[1], entry[1]))
return false;
this.source = undefined;
this.entries[entryIndex] = entry;
return true;
}
const childIndex = SortedIndex.next(entryIndex);
if (!this.hasChildren) {
this.source = undefined;
this.size++;
this.entries.splice(childIndex, 0, entry);
return true;
}
const child = this.children[childIndex];
const preSize = child.size;
const changed = child.addEntryInternal(entry);
if (!changed)
return false;
this.source = undefined;
this.size += child.size - preSize;
this.normalizeChildDecrease(childIndex);
return true;
}
removeInternal(key, otherwise) {
if (this.size === 0)
return OptLazy(otherwise);
const entryIndex = this.context.findIndex(key, this.entries);
if (entryIndex >= 0) {
this.source = undefined;
this.size--;
if (!this.hasChildren) {
const removed = this.entries.splice(entryIndex, 1);
return removed[0][1];
}
const leftChild = this.children[entryIndex];
const rightChild = this.children[entryIndex + 1];
const removed = this.entries[entryIndex];
if (leftChild.size >= rightChild.size) {
this.entries[entryIndex] = leftChild.deleteMax();
this.normalizeChildIncrease(entryIndex);
}
else {
this.entries[entryIndex] = rightChild.deleteMin();
this.normalizeChildIncrease(entryIndex + 1);
}
return removed[1];
}
if (!this.hasChildren)
return OptLazy(otherwise);
const childIndex = SortedIndex.next(entryIndex);
const child = this.children[childIndex];
const preSize = child.size;
const token = Symbol();
const oldValue = child.removeInternal(key, token);
if (token === oldValue)
return OptLazy(otherwise);
this.source = undefined;
this.size += child.size - preSize;
this.normalizeChildIncrease(childIndex);
return oldValue;
}
modifyAtInternal(key, options) {
const entryIndex = this.context.findIndex(key, this.entries);
if (entryIndex >= 0) {
if (undefined === options.ifExists)
return false;
const currentEntry = this.entries[entryIndex];
const currentValue = currentEntry[1];
const newValue = options.ifExists instanceof Function
? options.ifExists(currentValue, Token)
: options.ifExists;
if (newValue === currentValue)
return false;
if (Token === newValue) {
return Token !== this.removeInternal(key, Token);
}
this.source = undefined;
const newEntry = [key, newValue];
this.entries[entryIndex] = newEntry;
return true;
}
const childIndex = SortedIndex.next(entryIndex);
if (!this.hasChildren) {
if (undefined === options.ifNew)
return false;
const newValue = OptLazyOr(options.ifNew, Token);
if (Token === newValue)
return false;
this.source = undefined;
this.size++;
this.entries.splice(childIndex, 0, [key, newValue]);
return true;
}
const child = this.children[childIndex];
const preSize = child.size;
const changed = child.modifyAtInternal(key, options);
if (!changed)
return false;
this.source = undefined;
this.size += child.size - preSize;
this.normalizeChildDecrease(childIndex);
this.normalizeChildIncrease(childIndex);
return changed;
}
}
//# sourceMappingURL=builder.mjs.map