UNPKG

@rimbu/sorted

Version:

Immutable SortedMap and SortedSet implementations for TypeScript

366 lines (276 loc) 9.49 kB
import { Token } from '@rimbu/base'; import { OptLazy, OptLazyOr, type RelatedTo, Update } from '@rimbu/common'; import { Stream, type StreamSource } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; import type { SortedMap } from '@rimbu/sorted/map'; import type { SortedMapContext, SortedMapNode } from '@rimbu/sorted/map-custom'; import { SortedBuilder, SortedIndex } from '@rimbu/sorted/common'; export class SortedMapBuilder<K, V> extends SortedBuilder<readonly [K, V]> implements SortedMap.Builder<K, V> { constructor( readonly context: SortedMapContext<K>, public source?: undefined | SortedMap<K, V>, public _entries?: undefined | (readonly [K, V])[], public _children?: undefined | SortedMapBuilder<K, V>[], public size = source?.size ?? 0 ) { super(); } createNew( source?: undefined | SortedMap<K, V>, _entries?: undefined | (readonly [K, V])[], _children?: undefined | SortedMapBuilder<K, V>[], size?: undefined | number ): SortedMapBuilder<K, V> { return new SortedMapBuilder( this.context, source, _entries, _children, size ); } prepareMutate(): void { if (undefined === this._entries) { if (undefined !== this.source) { if (this.context.isSortedMapEmpty(this.source)) { this._entries = []; this._children = []; } else if (this.context.isSortedMapLeaf<K, V>(this.source)) { this._entries = this.source.entries.slice(); } else if (this.context.isSortedMapInner<K, V>(this.source)) { this._entries = this.source.entries.slice(); this._children = this.source.children.map( (child): SortedMapBuilder<K, V> => this.createNew(child) ); } } if (undefined === this._entries) { this._entries = []; } } } get children(): SortedMapBuilder<K, V>[] { this.prepareMutate(); return this._children!; } set children(value: SortedMapBuilder<K, V>[]) { this.prepareMutate(); this.source = undefined; this._children = value; } get = <UK, O>(key: RelatedTo<K, UK>, otherwise?: OptLazy<O>): V | O => { if (!this.context.comp.isComparable(key)) return OptLazy(otherwise) as O; 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) as O; }; // prettier-ignore hasKey = <UK,>(key: RelatedTo<K, UK>): boolean => { return Token !== this.get(key, Token); }; addEntry = (entry: readonly [K, V]): boolean => { this.checkLock(); const result = this.addEntryInternal(entry); this.normalize(); return result; }; addEntries = (source: StreamSource<readonly [K, V]>): boolean => { this.checkLock(); return Stream.from(source).filterPure({ pred: this.addEntry }).count() > 0; }; set = (key: K, value: V): boolean => { return this.addEntry([key, value]); }; removeKey = <UK, O>(key: RelatedTo<K, UK>, otherwise?: OptLazy<O>): V | O => { this.checkLock(); if (!this.context.comp.isComparable(key)) return OptLazy(otherwise) as O; const result = this.removeInternal(key, otherwise); this.normalize(); return result; }; // prettier-ignore removeKeys = <UK,>(keys: StreamSource<RelatedTo<K, UK>>): boolean => { this.checkLock(); if (isEmptyStreamSourceInstance(keys)) return false; const notFound = Symbol(); return ( Stream.from(keys) .mapPure(this.removeKey, notFound) .countElement(notFound, { negate: true }) > 0 ); }; modifyAt = ( key: K, options: { ifNew?: OptLazyOr<V, Token>; ifExists?: ((currentValue: V, remove: Token) => V | Token) | V; } ): boolean => { this.checkLock(); const result = this.modifyAtInternal(key, options); this.normalize(); return result; }; // prettier-ignore updateAt = <O,>(key: K, update: Update<V>, otherwise?: OptLazy<O>): V | O => { let result: V; let found = false; this.modifyAt(key, { ifExists: (value): V => { result = value; found = true; return Update(value, update); }, }); if (!found) return OptLazy(otherwise) as O; return result!; }; build = (): SortedMap<K, V> => { 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): SortedMapNode<K, V> => child.build() as SortedMapNode<K, V> ), this.size ); }; // prettier-ignore buildMapValues = <V2,>(f: (value: V, key: K) => V2): SortedMap<K, V2> => { if (undefined !== this.source) return this.source.mapValues(f); if (this.size === 0) return this.context.empty(); const newEntries = this.entries.map((entry): [K, V2] => [ entry[0], f(entry[1], entry[0]), ]); if (!this.hasChildren) { return this.context.leaf(newEntries); } return this.context.inner( newEntries, this.children.map( (c): SortedMapNode<K, V2> => c.buildMapValues(f) as SortedMapNode<K, V2> ), this.size ); }; addEntryInternal(entry: readonly [K, V]): boolean { 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<O>(key: K, otherwise?: OptLazy<O>): V | O { if (this.size === 0) return OptLazy(otherwise) as O; 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) as O; 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) as O; this.source = undefined; this.size += child.size - preSize; this.normalizeChildIncrease(childIndex); return oldValue; } modifyAtInternal( key: K, options: { ifNew?: OptLazyOr<V, Token>; ifExists?: ((currentValue: V, remove: Token) => V | Token) | V; } ): boolean { 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: [K, V] = [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<V, Token>(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; } }