UNPKG

@rimbu/sorted

Version:

Immutable SortedMap and SortedSet implementations for TypeScript

684 lines 23.3 kB
import { Arr, Entry, RimbuError, Token } from '@rimbu/base'; import { IndexRange, OptLazy, OptLazyOr, Range, TraverseState, Update, } from '@rimbu/common'; import { Stream } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; import { SortedEmpty, SortedIndex, SortedNonEmptyBase, innerDeleteMax, innerDeleteMin, innerDropInternal, innerGetAtIndex, innerMutateGetFromLeft, innerMutateGetFromRight, innerMutateGiveToLeft, innerMutateGiveToRight, innerMutateJoinLeft, innerMutateJoinRight, innerMutateSplitRight, innerNormalizeDownsizeChild, innerNormalizeIncreaseChild, innerStreamSliceIndex, innerTakeInternal, leafDeleteMax, leafDeleteMin, leafMutateGetFromLeft, leafMutateGetFromRight, leafMutateGiveToLeft, leafMutateGiveToRight, leafMutateJoinLeft, leafMutateJoinRight, leafMutateSplitRight, } from '@rimbu/sorted/common'; export class SortedMapEmpty extends SortedEmpty { constructor(context) { super(); this.context = context; } streamRange() { return Stream.empty(); } streamKeys() { return Stream.empty(); } streamValues() { return Stream.empty(); } streamSliceIndex() { return Stream.empty(); } minKey(otherwise) { return OptLazy(otherwise); } minValue(otherwise) { return OptLazy(otherwise); } maxKey(otherwise) { return OptLazy(otherwise); } maxValue(otherwise) { return OptLazy(otherwise); } get(key, otherwise) { return OptLazy(otherwise); } hasKey() { return false; } findIndex() { return -1; } getKeyAtIndex(index, otherwise) { return OptLazy(otherwise); } getValueAtIndex(index, otherwise) { return OptLazy(otherwise); } set(key, value) { return this.context.leaf([[key, value]]); } addEntry(entry) { return this.context.leaf([entry]); } addEntries(entries) { return this.context.from(entries); } removeKey() { return this; } removeKeys() { return this; } removeKeyAndGet() { return undefined; } modifyAt(atKey, options) { if (undefined !== options.ifNew) { const value = OptLazyOr(options.ifNew, Token); if (Token === value) return this; return this.context.leaf([[atKey, value]]); } return this; } mapValues() { return this; } updateAt() { return this; } slice() { return this; } toBuilder() { return this.context.builder(); } toString() { return `SortedMap()`; } toJSON() { return { dataType: this.context.typeTag, value: [], }; } } export class SortedMapNode 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 }; } streamKeys(options = {}) { return this.stream(options).map(Entry.first); } streamValues(options = {}) { return this.stream(options).map(Entry.second); } streamRange(keyRange, options = {}) { const { startIndex, endIndex } = this.getSliceRange(keyRange); return this.streamSliceIndex({ start: [startIndex, true], end: [endIndex, true], }, options); } minKey() { return this.min()[0]; } minValue() { return this.min()[1]; } maxKey() { return this.max()[0]; } maxValue() { return this.max()[1]; } hasKey(key) { const token = Symbol(); return token !== this.get(key, token); } getKeyAtIndex(index, otherwise) { const token = Symbol(); const result = this.getAtIndex(index, token); if (token === result) return OptLazy(otherwise); return result[0]; } getValueAtIndex(index, otherwise) { const token = Symbol(); const result = this.getAtIndex(index, token); if (token === result) return OptLazy(otherwise); return result[1]; } addEntry(entry) { return this.addInternal(entry).normalize().assumeNonEmpty(); } addEntries(entries) { if (isEmptyStreamSourceInstance(entries)) return this; const builder = this.toBuilder(); builder.addEntries(entries); return builder.build(); } modifyAt(atKey, options) { return this.modifyAtInternal(atKey, options).normalize(); } set(key, value) { return this.addEntry([key, value]); } updateAt(key, update) { if (!this.context.isValidKey(key)) return this; return this.modifyAt(key, { ifExists: (value) => Update(value, update), }).assumeNonEmpty(); } removeKey(key) { if (!this.context.isValidKey(key)) return this; return this.modifyAt(key, { ifExists: (_, remove) => remove, }); } removeKeys(keys) { if (isEmptyStreamSourceInstance(keys)) return this; const builder = this.toBuilder(); builder.removeKeys(keys); return builder.build(); } removeKeyAndGet(key) { if (!this.context.isValidKey(key)) return undefined; const token = Symbol(); let currentValue = token; const newMap = this.modifyAt(key, { ifExists: (value, remove) => { currentValue = value; return remove; }, }); if (token === currentValue) { return undefined; } return [newMap, currentValue]; } filter(pred, options = {}) { const builder = this.context.builder(); builder.addEntries(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], }); } toBuilder() { return this.context.createBuilder(this); } toString() { return this.stream().join({ start: 'SortedMap(', sep: ', ', end: ')', valueToString: (entry) => `${entry[0]} -> ${entry[1]}`, }); } toJSON() { return { dataType: this.context.typeTag, value: this.toArray(), }; } } export class SortedMapLeaf extends SortedMapNode { 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); } get(key, otherwise) { if (!this.context.isValidKey(key)) return OptLazy(otherwise); const index = this.context.findIndex(key, this.entries); if (index < 0) return OptLazy(otherwise); return this.entries[index][1]; } findIndex(key) { if (!this.context.comp.isComparable(key)) return -1; const index = this.context.findIndex(key, 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); } mapValues(mapFun) { const newEntries = this.entries.map((entry) => { const newValue = mapFun(entry[1], entry[0]); return [entry[0], newValue]; }); return this.context.leaf(newEntries); } toArray() { return this.entries.slice(); } // internal methods getInsertIndexOf(key) { return this.context.findIndex(key, this.entries); } addInternal(entry) { const index = this.context.findIndex(entry[0], this.entries); if (index >= 0) { const currentEntry = this.entries[index]; if (Object.is(currentEntry[1], entry[1])) return this; const newEntries = Arr.update(this.entries, index, entry); return this.copy(newEntries); } const insertIndex = SortedIndex.next(index); const newEntries = Arr.insert(this.entries, insertIndex, entry); return this.copy(newEntries); } modifyAtInternal(key, options) { const entryIndex = this.context.findIndex(key, this.entries); if (entryIndex >= 0) { if (undefined === options.ifExists) return this; const currentEntry = this.entries[entryIndex]; const currentValue = currentEntry[1]; const newValue = options.ifExists instanceof Function ? options.ifExists(currentValue, Token) : options.ifExists; if (Object.is(newValue, currentValue)) return this; if (Token === newValue) { const newEntries = Arr.splice(this.mutateEntries, entryIndex, 1); return this.copy(newEntries); } const newEntries = Arr.update(this.entries, entryIndex, [ key, newValue, ]); return this.copy(newEntries); } if (undefined === options.ifNew) return this; const newValue = OptLazyOr(options.ifNew, Token); if (Token === newValue) return this; const insertIndex = SortedIndex.next(entryIndex); const newEntries = Arr.insert(this.entries, insertIndex, [ key, newValue, ]); 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 SortedMapInner extends SortedMapNode { 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(); } get(key, otherwise) { if (!this.context.isValidKey(key)) return OptLazy(otherwise); const index = this.context.findIndex(key, this.entries); if (index >= 0) return this.entries[index][1]; const childIndex = SortedIndex.next(index); const child = this.children[childIndex]; return child.get(key, otherwise); } findIndex(key) { if (!this.context.comp.isComparable(key)) return -1; const index = this.context.findIndex(key, 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(key); 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 entryLength = this.entries.length; const { halt } = state; while (!state.halted && i < entryLength) { 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); } } mapValues(mapFun) { const newEntries = this.entries.map((entry) => { const newValue = mapFun(entry[1], entry[0]); return [entry[0], newValue]; }); const newChildren = this.children.map((child) => child.mapValues(mapFun)); return this.context.inner(newEntries, newChildren, this.size); } 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(key) { let index = 0; for (let i = 0; i < this.entries.length; i++) { const comp = this.context.comp.compare(key, this.entries[i][0]); const child = this.children[i]; if (comp < 0) { const insertIndex = child.getInsertIndexOf(key); 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(key); if (insertIndex < 0) return -index + insertIndex; return index + insertIndex; } addInternal(entry) { const entryIndex = this.context.findIndex(entry[0], this.entries); if (entryIndex >= 0) { const newEntries = Arr.update(this.entries, entryIndex, (currentEntry) => { if (Object.is(currentEntry[1], entry[1])) return currentEntry; return entry; }); return this.copy(newEntries); } const childIndex = SortedIndex.next(entryIndex); const child = this.children[childIndex]; const newChild = child.addInternal(entry); 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); } modifyAtInternal(key, options) { const entryIndex = this.context.findIndex(key, this.entries); if (entryIndex >= 0) { if (undefined === options.ifExists) return this; const currentEntry = this.entries[entryIndex]; const currentValue = currentEntry[1]; const newValue = options.ifExists instanceof Function ? options.ifExists(currentValue, Token) : options.ifExists; if (Object.is(newValue, currentValue)) return this; if (Token === newValue) { // 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); } // update inner entry const newEntry = [key, newValue]; const newEntries = Arr.update(this.entries, entryIndex, newEntry); return this.copy(newEntries); } const childIndex = SortedIndex.next(entryIndex); const child = this.children[childIndex]; const newChild = child.modifyAtInternal(key, options); 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); } 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); } 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