UNPKG

@rimbu/sorted

Version:

Immutable SortedMap and SortedSet implementations for TypeScript

850 lines (672 loc) 22.4 kB
import { Arr, RimbuError } from '@rimbu/base'; import { type ArrayNonEmpty, IndexRange, OptLazy, Range, type RelatedTo, type ToJSON, TraverseState, } from '@rimbu/common'; import { Stream, type StreamSource } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; import type { SortedSet } from '@rimbu/sorted/set'; import type { SortedSetContext } from '@rimbu/sorted/set-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<T = any> extends SortedEmpty implements SortedSet<T> { _NonEmptyType!: SortedSet.NonEmpty<T>; constructor(readonly context: SortedSetContext<T>) { super(); } streamRange(): Stream<T> { return Stream.empty(); } streamSliceIndex(): Stream<T> { return Stream.empty(); } has(): false { return false; } findIndex(): number { return -1; } add(value: T): SortedSet.NonEmpty<T> { return this.context.leaf([value]); } addAll(values: StreamSource<T>): SortedSet.NonEmpty<T> { return this.context.from(values); } remove(): this { return this; } removeAll(): this { return this; } slice(): this { return this; } union(other: StreamSource<T>): SortedSet<T> | any { if ( this.context.isSortedSetLeaf(other) || this.context.isSortedSetNode(other) ) { if (other.context === this.context) return other; } return this.context.from(other); } difference(): SortedSet<T> { return this.context.empty(); } intersect(): SortedSet<T> { return this.context.empty(); } symDifference(other: StreamSource<T>): SortedSet<T> { return this.union(other); } toBuilder(): SortedSet.Builder<T> { return this.context.builder(); } toString(): string { return `SortedSet()`; } toJSON(): ToJSON<T[]> { return { dataType: this.context.typeTag, value: [], }; } } export abstract class SortedSetNode<T> extends SortedNonEmptyBase<T, SortedSetNode<T>> implements SortedSet.NonEmpty<T> { _NonEmptyType!: SortedSetNode<T>; abstract get context(): SortedSetContext<T>; abstract get size(): number; abstract stream(options?: { reversed?: boolean }): Stream.NonEmpty<T>; abstract streamSliceIndex( range: IndexRange, options?: { reversed?: boolean } ): Stream<T>; abstract forEach( f: (value: T, index: number, halt: () => void) => void, options?: { state?: TraverseState } ): void; abstract has<U>(value: RelatedTo<T, U>): boolean; abstract findIndex(value: T): number; abstract min(): T; abstract max(): T; abstract toArray(): ArrayNonEmpty<T>; // internal methods abstract addInternal(value: T): SortedSetNode<T>; abstract removeInternal(value: T): SortedSetNode<T>; abstract getInsertIndexOf(value: T): number; abstract normalize(): SortedSet<T>; asNormal(): this { return this; } getSliceRange(range: Range<T>): { startIndex: number; endIndex: number } { 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: Range<T>, options?: { reversed?: boolean }): Stream<T> { const { startIndex, endIndex } = this.getSliceRange(range); return this.streamSliceIndex( { start: [startIndex, true], end: [endIndex, true], }, options ); } add(value: T): SortedSet.NonEmpty<T> { return this.addInternal(value).normalize().assumeNonEmpty(); } addAll(values: StreamSource<T>): SortedSet.NonEmpty<T> { if (isEmptyStreamSourceInstance(values)) return this; const builder = this.toBuilder(); builder.addAll(values); return builder.build().assumeNonEmpty(); } remove<U>(value: RelatedTo<T, U>): SortedSet<T> { if (!this.context.comp.isComparable(value)) return this; return this.removeInternal(value).normalize(); } removeAll<U>(values: StreamSource<RelatedTo<T, U>>): SortedSet<T> { if (isEmptyStreamSourceInstance(values)) return this; const builder = this.toBuilder(); builder.removeAll(values); return builder.build(); } filter( pred: (value: T, index: number, halt: () => void) => boolean, options: { negate?: boolean | undefined } = {} ): any { const builder = this.context.builder(); builder.addAll(this.stream().filter(pred, options)); if (builder.size === this.size) return this; return builder.build(); } take(amount: number): SortedSet<T> | any { 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: number): SortedSet<T> { 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: IndexRange): SortedSet<T> { 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: Range<T>): SortedSet<T> { const { startIndex, endIndex } = this.getSliceRange(range); return this.sliceIndex({ start: [startIndex, true], end: [endIndex, true], }); } union(other: StreamSource<T>): SortedSet<T> | any { if (other === this) return this; if (isEmptyStreamSourceInstance(other)) return this; const builder = this.toBuilder(); builder.addAll(other); return builder.build(); } difference(other: StreamSource<T>): SortedSet<T> { if (other === this) return this.context.empty(); if (isEmptyStreamSourceInstance(other)) return this; const builder = this.toBuilder(); builder.removeAll(other); return builder.build(); } intersect(other: StreamSource<T>): SortedSet<T> { 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: T | typeof done = thisIt.fastNext(done); let otherValue: T | typeof done = 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: T | typeof done; 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: StreamSource<T>): SortedSet<T> { 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(): SortedSet.Builder<T> { return this.context.createBuilder(this); } toString(): string { return this.stream().join({ start: 'SortedSet(', sep: ', ', end: ')' }); } toJSON(): ToJSON<T[]> { return { dataType: this.context.typeTag, value: this.toArray(), }; } } export class SortedSetLeaf<T> extends SortedSetNode<T> { constructor( readonly context: SortedSetContext<T>, public entries: readonly T[] ) { super(); } copy(entries: readonly T[]): SortedSetLeaf<T> { if (entries === this.entries) return this; return this.context.leaf(entries); } get size(): number { return this.entries.length; } stream(options: { reversed?: boolean } = {}): Stream.NonEmpty<T> { return Stream.fromArray(this.entries, options) as Stream.NonEmpty<T>; } streamSliceIndex( range: IndexRange, options: { reversed?: boolean } = {} ): Stream<T> { const { reversed = false } = options; return Stream.fromArray(this.entries, { range, reversed }); } min(): T { return this.entries[0]; } max(): T { return Arr.last(this.entries); } has<U>(value: RelatedTo<T, U>): boolean { if (!this.context.comp.isComparable(value)) return false; return this.context.findIndex(value, this.entries) >= 0; } findIndex(value: T): number { if (!this.context.comp.isComparable(value)) return -1; const index = this.context.findIndex(value, this.entries); return index < 0 ? -1 : index; } getAtIndex<O>(index: number, otherwise?: OptLazy<O>): T | O { if (index >= this.size || -index > this.size) { return OptLazy(otherwise) as O; } if (index < 0) { return this.getAtIndex(this.size + index, otherwise); } return this.entries[index]; } forEach( f: (value: T, index: number, halt: () => void) => void, options: { state?: TraverseState } = {} ): void { const { state = TraverseState() } = options; if (state.halted) return; Arr.forEach(this.entries, f, state); } toArray(): ArrayNonEmpty<T> { return this.entries.slice() as ArrayNonEmpty<T>; } // internal methods getInsertIndexOf(value: T): number { return this.context.findIndex(value, this.entries); } addInternal(value: T): SortedSetNode<T> { 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: T): SortedSetNode<T> { 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: number): SortedSetLeaf<T> { return this.context.leaf(this.entries.slice(0, amount)); } dropInternal(amount: number): SortedSetLeaf<T> { return this.context.leaf(this.entries.slice(amount)); } deleteMin(): [T, SortedSetLeaf<T>] { return leafDeleteMin<SortedSetLeaf<T>, T>(this); } deleteMax(): [T, SortedSetLeaf<T>] { return leafDeleteMax<SortedSetLeaf<T>, T>(this); } mutateSplitRight(index?: number): [T, SortedSetLeaf<T>] { return leafMutateSplitRight<SortedSetLeaf<T>, T>(this, index); } mutateGiveToLeft(left: SortedSetLeaf<T>, toLeft: T): [T, SortedSetLeaf<T>] { return leafMutateGiveToLeft(this, left, toLeft); } mutateGiveToRight( right: SortedSetLeaf<T>, toRight: T ): [T, SortedSetLeaf<T>] { return leafMutateGiveToRight(this, right, toRight); } mutateGetFromLeft(left: SortedSetLeaf<T>, toMe: T): [T, SortedSetLeaf<T>] { return leafMutateGetFromLeft(this, left, toMe); } mutateGetFromRight(right: SortedSetLeaf<T>, toMe: T): [T, SortedSetLeaf<T>] { return leafMutateGetFromRight(this, right, toMe); } mutateJoinLeft(left: SortedSetLeaf<T>, entry: T): void { return leafMutateJoinLeft(this, left, entry); } mutateJoinRight(right: SortedSetLeaf<T>, entry: T): void { return leafMutateJoinRight(this, right, entry); } normalize(): SortedSet<T> { 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<T> extends SortedSetNode<T> { constructor( readonly context: SortedSetContext<T>, public entries: readonly T[], public children: readonly SortedSetNode<T>[], public size: number ) { super(); } get mutateChildren(): SortedSetNode<T>[] { return this.children as SortedSetNode<T>[]; } copy( entries: readonly T[] = this.entries, children: readonly SortedSetNode<T>[] = this.children, size: number = this.size ): SortedSetInner<T> { if ( entries === this.entries && children === this.children && size === this.size ) return this; return this.context.inner(entries, children, size); } stream(options: { reversed?: boolean } = {}): Stream.NonEmpty<T> { const token = Symbol(); return Stream.zipAll( token, Stream.fromArray(this.children, options), Stream.fromArray(this.entries, options) ).flatMap(([child, e]): Stream.NonEmpty<T> => { if (token === child) RimbuError.throwInvalidStateError(); if (token === e) return child.stream(options); return child.stream(options).append(e); }) as Stream.NonEmpty<T>; } streamSliceIndex( range: IndexRange, options: { reversed?: boolean } = {} ): Stream<T> { const { reversed = false } = options; return innerStreamSliceIndex<T>(this, range, reversed); } min(): T { return this.children[0].min(); } max(): T { return Arr.last(this.children).max(); } has<U>(value: RelatedTo<T, U>): boolean { 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<U>(value); } findIndex(value: T): number { 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<O>(index: number, otherwise?: OptLazy<O>): T | O { return innerGetAtIndex<T, O>(this, index, otherwise); } forEach( f: (value: T, index: number, halt: () => void) => void, options: { state?: TraverseState } = {} ): void { 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(): ArrayNonEmpty<T> { let i = -1; let result: T[] = []; 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 as ArrayNonEmpty<T>; } // internal methods getInsertIndexOf(value: T): number { 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(): [T, SortedSetInner<T>] { return innerDeleteMin<SortedSetInner<T>, T>(this); } deleteMax(): [T, SortedSetInner<T>] { return innerDeleteMax<SortedSetInner<T>, T>(this); } mutateSplitRight(index?: number): [T, SortedSetInner<T>] { return innerMutateSplitRight<SortedSetInner<T>, T>(this, index); } mutateGiveToLeft(left: SortedSetInner<T>, toLeft: T): [T, SortedSetInner<T>] { return innerMutateGiveToLeft(this, left, toLeft); } mutateGiveToRight( right: SortedSetInner<T>, toRight: T ): [T, SortedSetInner<T>] { return innerMutateGiveToRight(this, right, toRight); } mutateGetFromLeft(left: SortedSetInner<T>, toMe: T): [T, SortedSetInner<T>] { return innerMutateGetFromLeft(this, left, toMe); } mutateGetFromRight( right: SortedSetInner<T>, toMe: T ): [T, SortedSetInner<T>] { return innerMutateGetFromRight(this, right, toMe); } mutateJoinLeft(left: SortedSetInner<T>, entry: T): void { return innerMutateJoinLeft(this, left, entry); } mutateJoinRight(right: SortedSetInner<T>, entry: T): void { return innerMutateJoinRight(this, right, entry); } normalizeDownsizeChild( childIndex: number, newChild: SortedSetNode<T>, newSize: number ): SortedSetInner<T> { return innerNormalizeDownsizeChild<SortedSetInner<T>, T>( this, childIndex, newChild, newSize ); } normalizeIncreaseChild( childIndex: number, newChild: SortedSetNode<T>, newSize: number ): SortedSetInner<T> { return innerNormalizeIncreaseChild<SortedSetInner<T>, T>( this, childIndex, newChild, newSize ); } addInternal(value: T): SortedSetInner<T> { 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: T): SortedSetNode<T> { 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: number): SortedSetNode<T> { return innerTakeInternal<SortedSetInner<T>, T>(this, amount); } dropInternal(amount: number): SortedSetNode<T> { return innerDropInternal<SortedSetInner<T>, T>(this, amount); } normalize(): SortedSet<T> { 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); } }