UNPKG

@rimbu/bimap

Version:

A bidirectional immutable Map of keys and values for TypeScript

420 lines (318 loc) 10.3 kB
import type { BiMapContext } from '@rimbu/bimap/custom'; import type { BiMap } from '@rimbu/bimap'; import type { RMap } from '@rimbu/collection-types/map'; import { EmptyBase, NonEmptyBase } from '@rimbu/collection-types/map-custom'; import { type ArrayNonEmpty, OptLazy, type RelatedTo, type ToJSON, TraverseState, Update, } from '@rimbu/common'; import { Stream, type StreamSource } from '@rimbu/stream'; import { isEmptyStreamSourceInstance } from '@rimbu/stream/custom'; export class BiMapEmpty<K = any, V = any> extends EmptyBase implements BiMap<K, V> { _NonEmptyType!: BiMap.NonEmpty<K, V>; constructor(readonly context: BiMapContext<K, V>) { super(); } get size(): 0 { return 0; } get keyValueMap(): RMap<K, V> { return this.context.keyValueContext.empty(); } get valueKeyMap(): RMap<V, K> { return this.context.valueKeyContext.empty(); } hasKey(): false { return false; } hasValue(): false { return false; } getValue<_, O>(key: K, otherwise?: OptLazy<O>): O { return OptLazy(otherwise) as O; } getKey<_, O>(value: V, otherwise?: OptLazy<O>): O { return OptLazy(otherwise) as O; } set(key: K, value: V): BiMap.NonEmpty<K, V> { return this.addEntry([key, value]); } addEntry(entry: readonly [K, V]): BiMap.NonEmpty<K, V> { return new BiMapNonEmptyImpl( this.context, this.context.keyValueContext.of(entry), this.context.valueKeyContext.of([entry[1], entry[0]]) ); } addEntries(entries: StreamSource<readonly [K, V]>): BiMap.NonEmpty<K, V> { return this.context.from(entries); } removeKey(): this { return this; } removeKeyAndGet(): undefined { return undefined; } removeKeys(): this { return this; } removeValue(): this { return this; } removeValueAndGet(): undefined { return undefined; } removeValues(): this { return this; } updateKeyAtValue(): this { return this; } updateValueAtKey(): this { return this; } streamKeys(): Stream<K> { return Stream.empty(); } streamValues(): Stream<V> { return Stream.empty(); } toBuilder(): BiMap.Builder<K, V> { return this.context.builder(); } toString(): string { return `BiMap()`; } toJSON(): ToJSON<(readonly [K, V])[], this['context']['typeTag']> { return { dataType: this.context.typeTag, value: [], }; } } export class BiMapNonEmptyImpl<K, V> extends NonEmptyBase<readonly [K, V]> implements BiMap.NonEmpty<K, V> { _NonEmptyType!: BiMap.NonEmpty<K, V>; constructor( readonly context: BiMapContext<K, V>, readonly keyValueMap: RMap.NonEmpty<K, V>, readonly valueKeyMap: RMap.NonEmpty<V, K> ) { super(); } copy( keyValueMap = this.keyValueMap, valueKeyMap = this.valueKeyMap ): BiMap.NonEmpty<K, V> { if (keyValueMap === this.keyValueMap && valueKeyMap === this.valueKeyMap) return this; return new BiMapNonEmptyImpl(this.context, keyValueMap, valueKeyMap); } copyE( keyValueMap: RMap<K, V> = this.keyValueMap, valueKeyMap: RMap<V, K> = this.valueKeyMap ): BiMap<K, V> { if (keyValueMap.nonEmpty() && valueKeyMap.nonEmpty()) { return new BiMapNonEmptyImpl(this.context, keyValueMap, valueKeyMap); } return this.context.empty(); } get size(): number { return this.keyValueMap.size; } asNormal(): this { return this; } stream(): Stream.NonEmpty<readonly [K, V]> { return this.keyValueMap.stream(); } streamKeys(): Stream.NonEmpty<K> { return this.keyValueMap.streamKeys(); } streamValues(): Stream.NonEmpty<V> { return this.valueKeyMap.streamKeys(); } hasKey<UK>(key: RelatedTo<K, UK>): boolean { const token = Symbol(); return token !== this.getValue(key, token); } hasValue<UV>(value: RelatedTo<V, UV>): boolean { const token = Symbol(); return token !== this.getKey(value, token); } getValue<UK, O>(key: RelatedTo<K, UK>, otherwise?: OptLazy<O>): V | O { return this.keyValueMap.get(key, otherwise!); } getKey<UV, O>(value: RelatedTo<V, UV>, otherwise?: OptLazy<O>): K | O { return this.valueKeyMap.get(value, otherwise!); } set(key: K, value: V): BiMap.NonEmpty<K, V> { return this.addEntry([key, value]); } addEntry(entry: readonly [K, V]): BiMap.NonEmpty<K, V> { const [key, value] = entry; const removeKeyResult = this.keyValueMap.removeKeyAndGet(key); const removeValueResult = this.valueKeyMap.removeKeyAndGet(value); if (undefined === removeKeyResult && undefined === removeValueResult) { // key and value were not present return this.copy( this.keyValueMap.addEntry(entry), this.valueKeyMap.set(value, key) ); } if (undefined !== removeKeyResult && undefined !== removeValueResult) { const [removedKeyValueMap, oldValue] = removeKeyResult; const [removedValueKeyMap, oldKey] = removeValueResult; // check if existing entry was not same if (Object.is(oldKey, key) && Object.is(oldValue, value)) return this; const newKeyValueMap = removedKeyValueMap .removeKey(oldKey) .addEntry(entry); const newValueKeyMap = removedValueKeyMap .removeKey(oldValue) .set(value, key); return this.copy(newKeyValueMap, newValueKeyMap); } const newKeyValueMap = ( undefined === removeValueResult ? this.keyValueMap : this.keyValueMap.removeKey(removeValueResult[1]) ).addEntry(entry); const newValueKeyMap = ( undefined === removeKeyResult ? this.valueKeyMap : this.valueKeyMap.removeKey(removeKeyResult[1]) ).set(value, key); return this.copy(newKeyValueMap, newValueKeyMap); } addEntries(entries: StreamSource<readonly [K, V]>): BiMap.NonEmpty<K, V> { if (isEmptyStreamSourceInstance(entries)) return this; const builder = this.toBuilder(); builder.addEntries(entries); return builder.build() as BiMap.NonEmpty<K, V>; } removeKey<UK>(key: RelatedTo<K, UK>): BiMap<K, V> { const removeKeyResult = this.keyValueMap.removeKeyAndGet(key); if (undefined === removeKeyResult) return this; const [newKeyValueMap, oldValue] = removeKeyResult; if (this.size === 1) return this.context.empty(); const newValueKeyMap = this.valueKeyMap.removeKey(oldValue!); return this.copy( newKeyValueMap.assumeNonEmpty(), newValueKeyMap.assumeNonEmpty() ); } removeKeyAndGet<UK>(key: RelatedTo<K, UK>): [BiMap<K, V>, V] | undefined { const removeKeyResult = this.keyValueMap.removeKeyAndGet(key); if (undefined === removeKeyResult) return undefined; const [newKeyValueMap, oldValue] = removeKeyResult; if (this.size === 1) return [this.context.empty(), oldValue]; const newValueKeyMap = this.valueKeyMap.removeKey(oldValue); return [ this.copy( newKeyValueMap.assumeNonEmpty(), newValueKeyMap.assumeNonEmpty() ), oldValue, ]; } removeKeys<UK>(keys: Stream<RelatedTo<K, UK>>): BiMap<K, V> { if (isEmptyStreamSourceInstance(keys)) return this; const builder = this.toBuilder(); builder.removeKeys(keys); return builder.build(); } removeValue<UV>(value: RelatedTo<V, UV>): BiMap<K, V> { const removeResult = this.valueKeyMap.removeKeyAndGet(value); if (undefined === removeResult) return this; const [newValueKeyMap, oldKey] = removeResult; if (this.size === 1) return this.context.empty(); const newKeyValueMap = this.keyValueMap.removeKey(oldKey!); return this.copy( newKeyValueMap.assumeNonEmpty(), newValueKeyMap.assumeNonEmpty() ); } removeValueAndGet<UV>(value: RelatedTo<V, UV>): [BiMap<K, V>, K] | undefined { const removeValueResult = this.valueKeyMap.removeKeyAndGet(value); if (undefined === removeValueResult) return undefined; const [newValueKeyMap, oldKey] = removeValueResult; if (this.size === 1) return [this.context.empty(), oldKey]; const newKeyValueMap = this.keyValueMap.removeKey(oldKey); return [ this.copy( newKeyValueMap.assumeNonEmpty(), newValueKeyMap.assumeNonEmpty() ), oldKey, ]; } removeValues<UV>(values: Stream<RelatedTo<V, UV>>): BiMap<K, V> { if (isEmptyStreamSourceInstance(values)) return this; const builder = this.toBuilder(); builder.removeValues(values); return builder.build(); } updateValueAtKey(key: K, valueUpdate: Update<V>): BiMap.NonEmpty<K, V> { const token = Symbol(); const currentValue = this.getValue(key, token); if (token === currentValue) return this; const newValue = Update(currentValue, valueUpdate); if (Object.is(newValue, currentValue)) return this; return this.set(key, newValue); } updateKeyAtValue(keyUpdate: Update<K>, value: V): BiMap.NonEmpty<K, V> { const token = Symbol(); const result = this.getKey(value, token); if (token === result) return this; const newKey = Update(result, keyUpdate); if (Object.is(newKey, result)) return this; return this.set(newKey, value); } forEach( f: (entry: readonly [K, V], index: number, halt: () => void) => void, options: { state?: TraverseState } = {} ): void { const { state = TraverseState() } = options; if (state.halted) return; this.keyValueMap.forEach(f, { state }); } filter( pred: (entry: readonly [K, V], index: number, halt: () => void) => boolean, options: { negate?: boolean } = {} ): BiMap<K, V> { const builder = this.context.builder(); builder.addEntries(this.stream().filter(pred, options)); if (builder.size === this.size) return this; return builder.build(); } toBuilder(): BiMap.Builder<K, V> { return this.context.createBuilder(this); } toArray(): ArrayNonEmpty<readonly [K, V]> { return this.keyValueMap.toArray(); } toString(): string { return this.stream().join({ start: `BiMap(`, sep: ', ', end: `)`, valueToString: (entry) => `${entry[0]} <-> ${entry[1]}`, }); } toJSON(): ToJSON<(readonly [K, V])[], this['context']['typeTag']> { return { dataType: this.context.typeTag, value: this.toArray(), }; } }