@rimbu/sorted
Version:
Immutable SortedMap and SortedSet implementations for TypeScript
585 lines • 20.3 kB
JavaScript
import { Arr, RimbuError } from '@rimbu/base';
import { IndexRange, OptLazy, Range, TraverseState, } from '@rimbu/common';
import { Stream } from '@rimbu/stream';
import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom';
import { SortedIndex, innerDeleteMax, innerDeleteMin, innerDropInternal, innerGetAtIndex, innerMutateGetFromLeft, innerMutateGetFromRight, innerMutateGiveToLeft, innerMutateGiveToRight, innerMutateJoinLeft, innerMutateJoinRight, innerMutateSplitRight, innerNormalizeDownsizeChild, innerNormalizeIncreaseChild, innerStreamSliceIndex, innerTakeInternal, leafDeleteMax, leafDeleteMin, leafMutateGetFromLeft, leafMutateGetFromRight, leafMutateGiveToLeft, leafMutateGiveToRight, leafMutateJoinLeft, leafMutateJoinRight, leafMutateSplitRight, SortedEmpty, SortedNonEmptyBase, } from '@rimbu/sorted/common';
export class SortedSetEmpty extends SortedEmpty {
constructor(context) {
super();
this.context = context;
}
streamRange() {
return Stream.empty();
}
streamSliceIndex() {
return Stream.empty();
}
has() {
return false;
}
findIndex() {
return -1;
}
add(value) {
return this.context.leaf([value]);
}
addAll(values) {
return this.context.from(values);
}
remove() {
return this;
}
removeAll() {
return this;
}
slice() {
return this;
}
union(other) {
if (this.context.isSortedSetLeaf(other) ||
this.context.isSortedSetNode(other)) {
if (other.context === this.context)
return other;
}
return this.context.from(other);
}
difference() {
return this.context.empty();
}
intersect() {
return this.context.empty();
}
symDifference(other) {
return this.union(other);
}
toBuilder() {
return this.context.builder();
}
toString() {
return `SortedSet()`;
}
toJSON() {
return {
dataType: this.context.typeTag,
value: [],
};
}
}
export class SortedSetNode extends SortedNonEmptyBase {
asNormal() {
return this;
}
getSliceRange(range) {
const { start, end } = Range.getNormalizedRange(range);
let startIndex = 0;
let endIndex = this.size - 1;
if (undefined !== start) {
const [startValue, startInclude] = start;
startIndex = this.getInsertIndexOf(startValue);
if (startIndex < 0)
startIndex = SortedIndex.next(startIndex);
else if (!startInclude)
startIndex++;
}
if (undefined !== end) {
const [endValue, endInclude] = end;
endIndex = this.getInsertIndexOf(endValue);
if (endIndex < 0)
endIndex = SortedIndex.prev(endIndex);
else if (!endInclude)
endIndex--;
}
return { startIndex, endIndex };
}
streamRange(range, options) {
const { startIndex, endIndex } = this.getSliceRange(range);
return this.streamSliceIndex({
start: [startIndex, true],
end: [endIndex, true],
}, options);
}
add(value) {
return this.addInternal(value).normalize().assumeNonEmpty();
}
addAll(values) {
if (isEmptyStreamSourceInstance(values))
return this;
const builder = this.toBuilder();
builder.addAll(values);
return builder.build().assumeNonEmpty();
}
remove(value) {
if (!this.context.comp.isComparable(value))
return this;
return this.removeInternal(value).normalize();
}
removeAll(values) {
if (isEmptyStreamSourceInstance(values))
return this;
const builder = this.toBuilder();
builder.removeAll(values);
return builder.build();
}
filter(pred, options = {}) {
const builder = this.context.builder();
builder.addAll(this.stream().filter(pred, options));
if (builder.size === this.size)
return this;
return builder.build();
}
take(amount) {
if (amount === 0)
return this.context.empty();
if (amount >= this.size || -amount > this.size)
return this;
if (amount < 0)
return this.drop(this.size + amount);
return this.takeInternal(amount).normalize();
}
drop(amount) {
if (amount === 0)
return this;
if (amount >= this.size || -amount > this.size)
return this.context.empty();
if (amount < 0)
return this.take(this.size + amount);
return this.dropInternal(amount).normalize();
}
sliceIndex(range) {
const indexRange = IndexRange.getIndicesFor(range, this.size);
if (indexRange === 'empty')
return this.context.empty();
if (indexRange === 'all')
return this;
const [start, end] = indexRange;
return this.drop(start).take(end - start + 1);
}
slice(range) {
const { startIndex, endIndex } = this.getSliceRange(range);
return this.sliceIndex({
start: [startIndex, true],
end: [endIndex, true],
});
}
union(other) {
if (other === this)
return this;
if (isEmptyStreamSourceInstance(other))
return this;
const builder = this.toBuilder();
builder.addAll(other);
return builder.build();
}
difference(other) {
if (other === this)
return this.context.empty();
if (isEmptyStreamSourceInstance(other))
return this;
const builder = this.toBuilder();
builder.removeAll(other);
return builder.build();
}
intersect(other) {
if (other === this)
return this;
if (isEmptyStreamSourceInstance(other))
return this.context.empty();
const builder = this.context.builder();
const otherIter = Stream.from(other)[Symbol.iterator]();
const done = Symbol('Done');
if (this.context.isSortedSetNode(other)) {
const thisIt = this[Symbol.iterator]();
let thisValue = thisIt.fastNext(done);
let otherValue = otherIter.fastNext(done);
const comp = this.context.comp;
while (true) {
if (done === thisValue || done === otherValue) {
break;
}
const result = comp.compare(thisValue, otherValue);
if (result === 0)
builder.add(thisValue);
if (result <= 0)
thisValue = thisIt.fastNext(done);
if (result >= 0)
otherValue = otherIter.fastNext(done);
}
}
else {
let value;
while (done !== (value = otherIter.fastNext(done))) {
if (this.has(value))
builder.add(value);
}
}
if (builder.size === this.size)
return this;
return builder.build();
}
symDifference(other) {
if (other === this)
return this.context.empty();
if (isEmptyStreamSourceInstance(other))
return this;
const builder = this.toBuilder();
Stream.from(other)
.filterPure({ pred: builder.remove, negate: true })
.forEach(builder.add);
return builder.build();
}
toBuilder() {
return this.context.createBuilder(this);
}
toString() {
return this.stream().join({ start: 'SortedSet(', sep: ', ', end: ')' });
}
toJSON() {
return {
dataType: this.context.typeTag,
value: this.toArray(),
};
}
}
export class SortedSetLeaf extends SortedSetNode {
constructor(context, entries) {
super();
this.context = context;
this.entries = entries;
}
copy(entries) {
if (entries === this.entries)
return this;
return this.context.leaf(entries);
}
get size() {
return this.entries.length;
}
stream(options = {}) {
return Stream.fromArray(this.entries, options);
}
streamSliceIndex(range, options = {}) {
const { reversed = false } = options;
return Stream.fromArray(this.entries, { range, reversed });
}
min() {
return this.entries[0];
}
max() {
return Arr.last(this.entries);
}
has(value) {
if (!this.context.comp.isComparable(value))
return false;
return this.context.findIndex(value, this.entries) >= 0;
}
findIndex(value) {
if (!this.context.comp.isComparable(value))
return -1;
const index = this.context.findIndex(value, this.entries);
return index < 0 ? -1 : index;
}
getAtIndex(index, otherwise) {
if (index >= this.size || -index > this.size) {
return OptLazy(otherwise);
}
if (index < 0) {
return this.getAtIndex(this.size + index, otherwise);
}
return this.entries[index];
}
forEach(f, options = {}) {
const { state = TraverseState() } = options;
if (state.halted)
return;
Arr.forEach(this.entries, f, state);
}
toArray() {
return this.entries.slice();
}
// internal methods
getInsertIndexOf(value) {
return this.context.findIndex(value, this.entries);
}
addInternal(value) {
const index = this.context.findIndex(value, this.entries);
if (index >= 0) {
const newEntries = Arr.update(this.entries, index, value);
return this.copy(newEntries);
}
const insertIndex = SortedIndex.next(index);
const newEntries = Arr.insert(this.entries, insertIndex, value);
return this.copy(newEntries);
}
removeInternal(value) {
const entryIndex = this.context.findIndex(value, this.entries);
if (entryIndex < 0)
return this;
const currentValue = this.entries[entryIndex];
if (this.context.comp.compare(currentValue, value) !== 0)
return this;
const newEntries = Arr.splice(this.mutateEntries, entryIndex, 1);
return this.copy(newEntries);
}
takeInternal(amount) {
return this.context.leaf(this.entries.slice(0, amount));
}
dropInternal(amount) {
return this.context.leaf(this.entries.slice(amount));
}
deleteMin() {
return leafDeleteMin(this);
}
deleteMax() {
return leafDeleteMax(this);
}
mutateSplitRight(index) {
return leafMutateSplitRight(this, index);
}
mutateGiveToLeft(left, toLeft) {
return leafMutateGiveToLeft(this, left, toLeft);
}
mutateGiveToRight(right, toRight) {
return leafMutateGiveToRight(this, right, toRight);
}
mutateGetFromLeft(left, toMe) {
return leafMutateGetFromLeft(this, left, toMe);
}
mutateGetFromRight(right, toMe) {
return leafMutateGetFromRight(this, right, toMe);
}
mutateJoinLeft(left, entry) {
return leafMutateJoinLeft(this, left, entry);
}
mutateJoinRight(right, entry) {
return leafMutateJoinRight(this, right, entry);
}
normalize() {
if (this.entries.length === 0)
return this.context.empty();
if (this.entries.length <= this.context.maxEntries)
return this;
const size = this.size;
const [upEntry, rightNode] = this.mutateSplitRight();
return this.context.inner([upEntry], [this, rightNode], size);
}
}
export class SortedSetInner extends SortedSetNode {
constructor(context, entries, children, size) {
super();
this.context = context;
this.entries = entries;
this.children = children;
this.size = size;
}
get mutateChildren() {
return this.children;
}
copy(entries = this.entries, children = this.children, size = this.size) {
if (entries === this.entries &&
children === this.children &&
size === this.size)
return this;
return this.context.inner(entries, children, size);
}
stream(options = {}) {
const token = Symbol();
return Stream.zipAll(token, Stream.fromArray(this.children, options), Stream.fromArray(this.entries, options)).flatMap(([child, e]) => {
if (token === child)
RimbuError.throwInvalidStateError();
if (token === e)
return child.stream(options);
return child.stream(options).append(e);
});
}
streamSliceIndex(range, options = {}) {
const { reversed = false } = options;
return innerStreamSliceIndex(this, range, reversed);
}
min() {
return this.children[0].min();
}
max() {
return Arr.last(this.children).max();
}
has(value) {
if (!this.context.comp.isComparable(value))
return false;
const index = this.context.findIndex(value, this.entries);
if (index >= 0)
return true;
const childIndex = SortedIndex.next(index);
const child = this.children[childIndex];
return child.has(value);
}
findIndex(value) {
if (!this.context.comp.isComparable(value))
return -1;
const index = this.context.findIndex(value, this.entries);
if (index >= 0)
return (this.children.slice(0, index + 1).reduce((x, y) => x + y.size, 0) +
index);
const childIndex = SortedIndex.next(index);
const child = this.children[childIndex];
const index$ = child.findIndex(value);
if (index$ >= 0) {
return (index$ +
(this.children.slice(0, childIndex).reduce((x, y) => x + y.size, 0) -
index -
1));
}
return -1;
}
getAtIndex(index, otherwise) {
return innerGetAtIndex(this, index, otherwise);
}
forEach(f, options = {}) {
const { state = TraverseState() } = options;
let i = -1;
const { halt } = state;
while (!state.halted && i < this.entries.length) {
if (i >= 0)
f(this.entries[i], state.nextIndex(), halt);
else {
const childIndex = SortedIndex.next(i);
this.children[childIndex].forEach(f, { state });
}
i = SortedIndex.next(i);
}
}
toArray() {
let i = -1;
let result = [];
while (i < this.entries.length) {
if (i >= 0)
result.push(this.entries[i]);
else {
const childIndex = SortedIndex.next(i);
result = result.concat(this.children[childIndex].toArray());
}
i = SortedIndex.next(i);
}
return result;
}
// internal methods
getInsertIndexOf(value) {
let index = 0;
for (let i = 0; i < this.entries.length; i++) {
const comp = this.context.comp.compare(value, this.entries[i]);
const child = this.children[i];
if (comp < 0) {
const insertIndex = child.getInsertIndexOf(value);
if (insertIndex < 0)
return -index + insertIndex;
return index + insertIndex;
}
index += child.size + 1;
if (comp === 0)
return index - 1;
}
const insertIndex = Arr.last(this.children).getInsertIndexOf(value);
if (insertIndex < 0)
return -index + insertIndex;
return index + insertIndex;
}
deleteMin() {
return innerDeleteMin(this);
}
deleteMax() {
return innerDeleteMax(this);
}
mutateSplitRight(index) {
return innerMutateSplitRight(this, index);
}
mutateGiveToLeft(left, toLeft) {
return innerMutateGiveToLeft(this, left, toLeft);
}
mutateGiveToRight(right, toRight) {
return innerMutateGiveToRight(this, right, toRight);
}
mutateGetFromLeft(left, toMe) {
return innerMutateGetFromLeft(this, left, toMe);
}
mutateGetFromRight(right, toMe) {
return innerMutateGetFromRight(this, right, toMe);
}
mutateJoinLeft(left, entry) {
return innerMutateJoinLeft(this, left, entry);
}
mutateJoinRight(right, entry) {
return innerMutateJoinRight(this, right, entry);
}
normalizeDownsizeChild(childIndex, newChild, newSize) {
return innerNormalizeDownsizeChild(this, childIndex, newChild, newSize);
}
normalizeIncreaseChild(childIndex, newChild, newSize) {
return innerNormalizeIncreaseChild(this, childIndex, newChild, newSize);
}
addInternal(value) {
const entryIndex = this.context.findIndex(value, this.entries);
if (entryIndex >= 0) {
const newEntries = Arr.update(this.entries, entryIndex, value);
return this.copy(newEntries);
}
const childIndex = SortedIndex.next(entryIndex);
const child = this.children[childIndex];
const newChild = child.addInternal(value);
if (newChild === child)
return this;
const newSize = this.size + newChild.size - child.size;
if (newChild.entries.length <= this.context.maxEntries) {
// no need to shift
const newChildren = Arr.update(this.children, childIndex, newChild);
return this.copy(undefined, newChildren, newSize);
}
return this.normalizeDownsizeChild(childIndex, newChild, newSize);
}
removeInternal(value) {
const entryIndex = this.context.findIndex(value, this.entries);
if (entryIndex >= 0) {
const currentValue = this.entries[entryIndex];
if (!Object.is(currentValue, value))
return this;
// remove inner entry
const leftChild = this.children[entryIndex];
const rightChild = this.children[entryIndex + 1];
if (leftChild.entries.length >= rightChild.entries.length) {
const [max, newLeft] = leftChild.deleteMax();
const newEntries = Arr.update(this.entries, entryIndex, max);
const newSelf = this.copy(newEntries);
return newSelf.normalizeIncreaseChild(entryIndex, newLeft, this.size - 1);
}
const [min, newRight] = rightChild.deleteMin();
const newEntries = Arr.update(this.entries, entryIndex, min);
const newSelf = this.copy(newEntries);
return newSelf.normalizeIncreaseChild(entryIndex + 1, newRight, this.size - 1);
}
const childIndex = SortedIndex.next(entryIndex);
const child = this.children[childIndex];
const newChild = child.removeInternal(value);
const newSize = this.size + newChild.size - child.size;
if (newChild.entries.length < this.context.minEntries) {
return this.normalizeIncreaseChild(childIndex, newChild, newSize);
}
if (newChild.entries.length > this.context.maxEntries) {
return this.normalizeDownsizeChild(childIndex, newChild, newSize);
}
const newChildren = Arr.update(this.children, childIndex, newChild);
return this.copy(undefined, newChildren, this.size + newChild.size - child.size);
}
takeInternal(amount) {
return innerTakeInternal(this, amount);
}
dropInternal(amount) {
return innerDropInternal(this, amount);
}
normalize() {
if (this.entries.length === 0)
return this.children[0].normalize();
if (this.entries.length <= this.context.maxEntries)
return this;
const size = this.size;
const [upEntry, rightNode] = this.mutateSplitRight();
return this.copy([upEntry], [this, rightNode], size);
}
}
//# sourceMappingURL=immutable.mjs.map