UNPKG

@rimbu/bimap

Version:

A bidirectional immutable Map of keys and values for TypeScript

233 lines (174 loc) 6.3 kB
import type { BiMapContext, BiMapNonEmptyImpl } from '@rimbu/bimap/custom'; import type { BiMap } from '@rimbu/bimap'; import { RimbuError } from '@rimbu/base'; import type { RMap } from '@rimbu/collection-types/map'; import { OptLazy, type RelatedTo, TraverseState } from '@rimbu/common'; import { Stream, type StreamSource } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; export class BiMapBuilder<K, V> implements BiMap.Builder<K, V> { constructor( readonly context: BiMapContext<K, V>, public source?: BiMapNonEmptyImpl<K, V> ) {} _keyValueMap?: RMap.Builder<K, V>; _valueKeyMap?: RMap.Builder<V, K>; _lock = 0; checkLock(): void { if (this._lock) RimbuError.throwModifiedBuilderWhileLoopingOverItError(); } get keyValueMap(): RMap.Builder<K, V> { if (undefined === this._keyValueMap) { if (undefined === this.source) { this._keyValueMap = this.context.keyValueContext.builder(); this._valueKeyMap = this.context.valueKeyContext.builder(); } else { this._keyValueMap = this.source.keyValueMap.toBuilder(); this._valueKeyMap = this.source.valueKeyMap.toBuilder(); } } return this._keyValueMap; } get valueKeyMap(): RMap.Builder<V, K> { if (undefined === this._valueKeyMap) { if (undefined === this.source) { this._keyValueMap = this.context.keyValueContext.builder(); this._valueKeyMap = this.context.valueKeyContext.builder(); } else { this._keyValueMap = this.source.keyValueMap.toBuilder(); this._valueKeyMap = this.source.valueKeyMap.toBuilder(); } } return this._valueKeyMap; } get size(): number { return this.source?.size ?? this.keyValueMap.size; } get isEmpty(): boolean { return this.size === 0; } getValue = <UK, O>(key: RelatedTo<K, UK>, otherwise?: OptLazy<O>): V | O => { if (undefined !== this.source) return this.source.getValue(key, otherwise); return this.keyValueMap.get(key, otherwise!); }; // prettier-ignore hasKey = <UK,>(key: RelatedTo<K, UK>): boolean => { const token = Symbol(); return token !== this.getValue(key, token); }; getKey = <UV, O>(value: RelatedTo<V, UV>, otherwise?: OptLazy<O>): K | O => { if (undefined !== this.source) return this.source.getKey(value, otherwise); return this.valueKeyMap.get(value, otherwise!); }; // prettier-ignore hasValue = <UV,>(value: RelatedTo<V, UV>): boolean => { const token = Symbol(); return token !== this.getKey(value, token); }; set = (key: K, value: V): boolean => { return this.addEntry([key, value]); }; addEntry = (entry: readonly [K, V]): boolean => { this.checkLock(); const [key, value] = entry; const token = Symbol(); const oldValue = this.keyValueMap.get(key, token); const oldKey = this.valueKeyMap.get(value, token); if (token !== oldKey && token !== oldValue) { if (Object.is(oldKey, key) && Object.is(oldValue, value)) return false; this.keyValueMap.removeKey(oldKey); this.keyValueMap.addEntry(entry); this.valueKeyMap.removeKey(oldValue); this.valueKeyMap.set(value, key); } else if (token !== oldValue) { if (!Object.is(oldValue, value)) { this.valueKeyMap.removeKey(oldValue); this.valueKeyMap.set(value, key); } this.keyValueMap.addEntry(entry); } else if (token !== oldKey) { if (!Object.is(oldKey, key)) { this.keyValueMap.removeKey(oldKey); this.keyValueMap.addEntry(entry); } this.valueKeyMap.set(value, key); } else { this.keyValueMap.addEntry(entry); this.valueKeyMap.set(value, key); } this.source = undefined; return true; }; addEntries = (source: StreamSource<readonly [K, V]>): boolean => { this.checkLock(); return Stream.from(source).filterPure({ pred: this.addEntry }).count() > 0; }; removeKey = <UK, O>(key: RelatedTo<K, UK>, otherwise?: OptLazy<O>): V | O => { this.checkLock(); if (!this.context.keyValueContext.isValidKey(key)) { return OptLazy(otherwise) as O; } const token = Symbol(); const value = this.keyValueMap.removeKey(key, token); if (token === value) return OptLazy(otherwise) as O; this.valueKeyMap.removeKey(value); this.source = undefined; return value; }; // 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 ); }; removeValue = <UV, O>( value: RelatedTo<V, UV>, otherwise?: OptLazy<O> ): K | O => { this.checkLock(); if (!this.context.valueKeyContext.isValidKey(value)) { return OptLazy(otherwise) as O; } const token = Symbol(); const key = this.valueKeyMap.removeKey(value, token); if (token === key) return OptLazy(otherwise) as O; this.keyValueMap.removeKey(key); this.source = undefined; return key; }; // prettier-ignore removeValues = <UV,>(values: StreamSource<RelatedTo<V, UV>>): boolean => { this.checkLock(); if (isEmptyStreamSourceInstance(values)) return false; const notFound = Symbol(); return ( Stream.from(values) .mapPure(this.removeValue, notFound) .countElement(notFound, { negate: true }) > 0 ); }; forEach = ( f: (entry: readonly [K, V], index: number, halt: () => void) => void, options: { state?: TraverseState } = {} ): void => { const { state = TraverseState() } = options; this._lock++; if (!this.isEmpty && !state.halted) { if (undefined !== this.source) this.source.forEach(f, { state }); else this.keyValueMap.forEach(f, { state }); } this._lock--; }; build = (): BiMap<K, V> => { if (undefined !== this.source) return this.source; if (this.size === 0) return this.context.empty(); return this.context.createNonEmptyImpl( this.keyValueMap.build().assumeNonEmpty(), this.valueKeyMap.build().assumeNonEmpty() ); }; }