Skip to content

Instantly share code, notes, and snippets.

@Tyriar
Created May 12, 2022 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tyriar/dada94b6e8a10f60b67a38a2dd205925 to your computer and use it in GitHub Desktop.
Save Tyriar/dada94b6e8a10f60b67a38a2dd205925 to your computer and use it in GitHub Desktop.
A map where the value is accessed via 2 independent keys
interface IDualKeyMap<K, V> {
clear(): void;
delete(keyA: K, keyB: K): boolean;
forEach(callbackfn: (value: V, keyA: K, keyB: K, map: IDualKeyMap<K, V>) => void, thisArg?: any): void;
get(keyA: K, keyB: K): V | undefined;
has(keyA: K, keyB: K): boolean;
set(keyA: K, keyB: K, value: V): this;
readonly size: number;
}
/**
* A Map where the value is accessed via 2 independent keys. This essentially wraps a
* `Map<K, Map<K, V>>` and makes it more convenient to use.
*/
export class DualKeyMap<K, V> implements IDualKeyMap<K, V> {
private _data: Map<K, Map<K, V>> = new Map();
private _ttlTimeout: number | undefined;
public get size(): number {
let result = 0;
for (const inner of this._data.values()) {
result += inner.size;
}
return result;
}
/**
* @param _ttlMs An optional amount of milliseconds to wait to clear the map, debounced after
* every operation.
*/
constructor(
private readonly _ttlMs?: number
) {
}
public clear(): void {
this._clearTtl();
this._data.clear();
}
public delete(keyA: K, keyB: K): boolean {
this._triggerTtl();
const inner = this._data.get(keyA);
if (!inner) {
return false;
}
return inner.delete(keyB);
}
public forEach(callbackfn: (value: V, keyA: K, keyB: K, map: DualKeyMap<K, V>) => void, thisArg?: any): void {
this._triggerTtl();
const fn = callbackfn.bind(thisArg ?? this);
for (const [keyA, inner] of this._data.entries()) {
for (const [keyB, value] of inner.entries()) {
fn(value, keyA, keyB, this);
}
}
}
public get(keyA: K, keyB: K): V | undefined {
this._triggerTtl();
const inner = this._data.get(keyA);
if (!inner) {
return undefined;
}
return inner.get(keyB);
}
public has(keyA: K, keyB: K): boolean {
this._triggerTtl();
const inner = this._data.get(keyA);
if (!inner) {
return false;
}
return inner.has(keyB);
}
public set(keyA: K, keyB: K, value: V): this {
this._triggerTtl();
let inner = this._data.get(keyA);
if (!inner) {
inner = new Map();
}
this._data.set(keyA, inner);
inner.set(keyB, value);
return this;
}
private _clearTtl(): void {
if (this._ttlTimeout !== undefined) {
clearTimeout(this._ttlTimeout);
this._ttlTimeout = undefined;
}
}
private _triggerTtl(): void {
if (this._ttlMs === undefined) {
return;
}
this._clearTtl();
this._ttlTimeout = setTimeout(() => {
this.clear();
}, this._ttlMs);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment