UNPKG

@rimbu/sorted

Version:

Immutable SortedMap and SortedSet implementations for TypeScript

585 lines 20.3 kB
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