/*! \brief HashMap container. Map-like (\see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) container with equal by value semantics. \todo Investigate usage of buckets instead object-like. */ import { create } from '../core/object'; import { isIterable, isArrayLike } from '../core/traits'; type Node = { key: K; value: V; next: Node | undefined; }; export type Hash = (key: K) => number; export type Equal = (lhs: K, rhs: K) => boolean; export class HashMap { protected _entries = create | undefined>(); protected _size = 0; protected _hash: Hash; protected _equal: Equal; constructor(hashfn: Hash, equalfn: Equal, entries?: ArrayLike<[K, V]> | Iterable<[K, V]>) { this._hash = hashfn; this._equal = equalfn; if (isIterable(entries)) { for (const [key, value] of entries) this.set(key, value); } else if (isArrayLike(entries)) { for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i]; this.set(key, value); } } } get size() { return this._size; } get(key: K) { for (let node = this._entries[this._hash(key)]; node !== undefined; node = node.next) if (this._equal(key, node.key)) return node.value; } set(key: K, value: V) { const hash = this._hash(key); const head = this._entries[hash]; if (head === undefined) { this._entries[hash] = { key, value, next: undefined }; this._size++; return this; } for (let node = head; true;) { if (this._equal(key, node.key)) { node.value = value; return this; } const next = node.next; if (next === undefined) { node.next = { key, value, next: undefined }; this._size++; return this; } node = next; } } has(key: K) { for (let node = this._entries[this._hash(key)]; node !== undefined; node = node.next) if (this._equal(key, node.key)) return true; return false; } delete(key: K) { const hash = this._hash(key); const entries = this._entries; for (let node = entries[hash], prev: Node | undefined; node !== undefined;) { const next = node.next; if (this._equal(key, node.key)) { if (prev === undefined) { if (next === undefined) delete entries[hash]; else entries[hash] = next; } else prev.next = next; this._size--; return true; } prev = node; node = next; } return false; } clear() { for (const key of this.keys()) this.delete(key); } *keys() { const entries = this._entries; for (const key in entries) for (let node = entries[key]; node !== undefined; node = node.next) yield node.key; } *values() { const entries = this._entries; for (const key in entries) for (let node = entries[key]; node !== undefined; node = node.next) yield node.value; } *entries() { const entries = this._entries; for (const key in entries) for (let node = entries[key]; node !== undefined; node = node.next) yield [node.key, node.value] as [K, V]; } forEach(callbackfn: (value: V, key: K, map: HashMap) => void, thisArg?: any) { const entries = this._entries; for (const key in entries) for (let node = entries[key]; node !== undefined; node = node.next) callbackfn.call(thisArg, node.value, node.key, this); } [Symbol.iterator]() { return this.entries(); } }