@rimbu/bimap
Version:
A bidirectional immutable Map of keys and values for TypeScript
420 lines (318 loc) • 10.3 kB
text/typescript
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(),
};
}
}