// FlexMap, FlexSet: like built-in Map and Set, but with a // canonicalization function which gives us the possibility of a // coarser equivalence than the identity equivalence used in Map and // Set. // A Canonicalizer represents the equivalence you have in mind. For // // c: Canonicalizer // eqv: Equivalence // v1: V // v2: V // // where `eqv` is the equivalence you want, // // eqv(v1, v2) ⇔ c(v1) === c(v2) // export type Canonicalizer = (v: V) => string; export type Equivalence = (v1: V, v2: V) => boolean; export type IdentityMap = Map; export type IdentitySet = Set; export const IdentityMap = Map; export const IdentitySet = Set; export function _iterMap(i: Iterator | undefined, f : (s: S) => T): IterableIterator { if (!i) return void 0; const _f = (r: IteratorResult): IteratorResult => { const { done, value } = r; return { done, value: done ? void 0 : f(value) }; }; return { next: (v?: any): IteratorResult => _f(i.next(v)), return: (v?: any): IteratorResult => _f(i.return(v)), throw: (e?: any): IteratorResult => _f(i.throw(e)), [Symbol.iterator]() { return this; }, }; } export class FlexMap implements Map { readonly items: Map; readonly canonicalizer: Canonicalizer; constructor(c: Canonicalizer, items?: Iterable) { this.canonicalizer = c; this.items = new Map((items === void 0) ? void 0 : _iterMap(items[Symbol.iterator](), ([k, v]) => [this._key(k), [k, v]])); } _key(k: K): string { return this.canonicalizer(k); } get(k: K, defaultValue?: V): V | undefined { const e = this.items.get(this._key(k)); return (e === void 0) ? defaultValue : e[1]; } set(k: K, v: V): this { this.items.set(this._key(k), [k, v]); return this; } forEach(f: > (v: V, k: K, map: T) => void, thisArg?: any) { this.items.forEach(([k, v]) => f.call(thisArg, v, k, this)); } entries(): IterableIterator<[K, V]> { return this.items.values(); } keys(): IterableIterator { return _iterMap(this.items.values(), ([k, _v]) => k); } values(): IterableIterator { return _iterMap(this.items.values(), ([_k, v]) => v); } delete(k: K): boolean { return this.items.delete(this._key(k)); } clear() { this.items.clear(); } has(k: K): boolean { return this.items.has(this._key(k)); } get size(): number { return this.items.size; } [Symbol.iterator](): IterableIterator<[K, V]> { return this.items.values(); } get [Symbol.toStringTag]() { return 'FlexMap'; } equals(other: any, eqv: Equivalence = (v1, v2) => v1 === v2): boolean { if (!('size' in other && 'has' in other && 'get' in other)) return false; if (this.size !== other.size) return false; for (let [k, v] of this.items.values()) { if (!other.has(k)) return false; if (!eqv(v, other.get(k))) return false; } return true; } update(key: K, f: (oldValue?: V) => V | undefined, defaultValue?: V, eqv: Equivalence = (v1, v2) => v1 === v2): number { const ks = this._key(key); if (this.items.has(ks)) { const oldValue = this.items.get(ks)[1]; const newValue = f(oldValue); if (newValue === void 0) { this.items.delete(ks); return -1; } else { if (!eqv(newValue, oldValue)) this.items.set(ks, [key, newValue]); return 0; } } else { const newValue = f(defaultValue); if (newValue === void 0) { return 0; } else { this.items.set(ks, [key, newValue]); return 1; } } } canonicalKeys(): IterableIterator { return this.items.keys(); } } export class FlexSet implements Set { readonly items: Map; readonly canonicalizer: Canonicalizer; constructor(c: Canonicalizer, items?: Iterable) { this.canonicalizer = c; this.items = new Map((items === void 0) ? void 0 : _iterMap(items[Symbol.iterator](), (v) => [this._key(v), v])); } _key(v: V): string { return this.canonicalizer(v); } has(v: V): boolean { return this.items.has(this._key(v)); } get(v: V): {item: V} | null { const vs = this._key(v); if (this.items.has(vs)) { return {item: this.items[vs]}; } else { return null; } } add(v: V): this { this.items[this._key(v)] = v; return this; } forEach(f: >(v: V, v2: V, set: T) => void, thisArg?: any) { this.items.forEach((v) => f.call(thisArg, v, v, this)); } entries(): IterableIterator<[V, V]> { return _iterMap(this.items.values(), (v) => [v, v]); } keys(): IterableIterator { return this.items.values(); } values(): IterableIterator { return this.items.values(); } delete(v: V): boolean { return this.items.delete(this._key(v)); } clear() { this.items.clear(); } get size(): number { return this.items.size; } [Symbol.iterator](): IterableIterator { return this.items.values(); } get [Symbol.toStringTag]() { return 'FlexSet'; } equals(other: any): boolean { if (!('size' in other && 'has' in other)) return false; if (this.size !== other.size) return false; for (let v of this.items.values()) { if (!other.has(v)) return false; } return true; } canonicalValues(): IterableIterator { return this.items.keys(); } union(other: Set): FlexSet { const result = new FlexSet(this.canonicalizer, this); for (let k of other) result.add(k); return result; } intersect(other: Set): FlexSet { const result = new FlexSet(this.canonicalizer); for (let k of this) if (other.has(k)) result.add(k); return result; } subtract(other: Set): FlexSet { const result = new FlexSet(this.canonicalizer); for (let k of this) if (!other.has(k)) result.add(k); return result; } }