/** * Intended as a typescript-friendly replacement for {[k: string]: boolean} that allows us to specify what the key type should be ( * rather than allowing any keyType.toString() to be a valid key, and without going through the trouble of declaring distinguishable * types for each key type we want to use). Also serves as a slightly different version of ES6 native Set(), which is hardcoded * to use === for object referential equality. * * NOTE: this assume hash() is a strong test for equality, i.e. 2 objects are considered equal if and only if their hashes are the same!!! * TODO: write StrictHashSet<K extends {hash(): string, equals(k: K): boolean}> to handle custom equality checks */ export class HashSet<K extends { hash(): string }> { private _values: HashMap<K, K>; constructor(initialValues: K[] = []) { this._values = new HashMap<K, K>(); for (const value of initialValues) { this.put(value); } } remove(key: K): void { this._values.remove(key); } put(key: K): void { this._values.put(key, key); } get(key: K): boolean { return this._values.get(key) !== undefined; } contains(key: K): boolean { return this._values.contains(key); } values(): K[] { return this._values.values(); } // hash(): string { // return this._values.hashKeyset(); // } clone(): HashSet<K> { let n = new HashSet<K>(); n._values = this._values.clone() return n; } size(): number { return this._values.size(); } equals(other: HashSet<K> | undefined | null) { if (other === undefined || other === null) { return false; } if (this.size() !== other.size()) { return false; } for (let k of this.values()) { if (!other.contains(k)) { return false; } } return true; } // *[Symbol.iterator]() { // // construct a new iterator. note that as usual // for (let key of Object.keys(this._values)) { // yield key; // } // } } /** * Intended as a typescript-friendly replacement for {[k: string]: V} that allows us to specify what the key type should be ( * rather than allowing any keyType.toString() to be a valid key, and without going through the trouble of declaring distinguishable * types for each key type we want to use). Also serves as a slightly different version of ES6 native Map(), which is hardcoded * to use === for object referential equality. * * NOTE: this assume hash() is a strong test for equality, i.e. 2 objects are considered equal if and only if their hashes are the same!!! * TODO: write StrictHashMap<K extends {hash(): string, equals(K): boolean}> to handle custom equality checks */ export class HashMap<K extends { hash(): string }, V> { protected _values: { [key: string]: V } = {}; put(key: K, value: V) { this._values[key.hash()] = value; } remove(key: K): void { delete this._values[key.hash()]; } get(key: K): V | undefined { return this._values[key.hash()]; } contains(key: K): boolean { // V may be an undefined type return this.get(key) !== undefined && key.hash() in this._values; } values(): V[] { return Object.values(this._values); // return Object.keys(this._values).map(key => this._values[key]); // why grant??? } // *[Symbol.iterator]() { // // construct a new iterator. note that as usual editing the object during iteration is not supported // for (let key of Object.keys(this._values)) { // yield key; // } // } // hashes only the keys - use HashableHashMap if you know that the value type here is also hashable // hashKeyset(): string { // const hashes: number[] = Object.keys(this._values).map(s => hashCode(s)); // let code: number = hashes.reduce((pv, cv) => pv + cv); // return code.toString(); // } size(): number { return Object.keys(this._values).length; } clone(): HashMap<K, V> { let n = new HashMap<K, V>(); n._values = { ...this._values }; return n; } } export class HashableHashMap<K extends { hash(): string }, V extends { hash(): string }> extends HashMap<K, V> { hash(): string { const hashes: number[] = Object.entries(this._values).map(([s, v]) => hashCode(s) + hashCode(v.hash())); let code: number = hashes.reduce((pv, cv) => pv + cv); return code.toString(); } } /** * Same as HashMap, but actually stores the keys used to key the hashmap, instead of just their hashes. * Allows iteration over the full key-value pair set. */ export class KeyedHashMap<K extends { hash(): string }, V>{ private _kvalues: { [key: string]: [K, V] } = {}; put(key: K, value: V) { this._kvalues[key.hash()] = [key, value]; } remove(key: K): void { delete this._kvalues[key.hash()]; } get(key: K): V | undefined { return this._kvalues[key.hash()]?.[1]; } contains(key: K): boolean { // V may be an undefined type return this.get(key) !== undefined && key.hash() in this._kvalues; } keys(): K[] { return Object.keys(this._kvalues).map(key => this._kvalues[key][0]); } entries(): ([K, V])[] { return Object.keys(this._kvalues).map(key => this._kvalues[key]); } values(): V[] { return Object.keys(this._kvalues).map(key => this._kvalues[key][1]); } hashKeyset(): string { const hashes: number[] = Object.keys(this._kvalues).map(s => hashCode(s)); let code: number = hashes.reduce((pv, cv) => pv + cv); return code.toString(); } clone(): KeyedHashMap<K, V> { let n = new KeyedHashMap<K, V>(); n._kvalues = { ...this._kvalues }; return n; } size(): number { return Object.keys(this._kvalues).length; } } export class DefaultHashMap<K extends { hash(): string }, V> { private _values: { [key: string]: V } = {}; private _makeDefault: () => V; constructor(makeDefaultValue: () => V) { this._makeDefault = makeDefaultValue; } put(key: K, value: V) { this._values[key.hash()] = value; } get(key: K): V { if (this._values[key.hash()] === undefined) { this._values[key.hash()] = this._makeDefault(); } return this._values[key.hash()]; } } // Hash a string to a number. source: https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0 export function hashCode(s: string): number { let h = 0; for (let i = 0; i < s.length; i++) { h = Math.imul(31, h) + s.charCodeAt(i) | 0; } return h; } // declare global { // interface Array<T extends { hash(): string }> { // hash(): string // } // // interface Number { // hash(): string // } // // interface String { // hash(): String // } // } // // Array.prototype.hash = function () { // return hashArray(this); // } // // Number.prototype.hash = function () { // return this.toString(); // } // // String.prototype.hash = function () { // return this; // } // // function hashArray<T extends { hash(): string }>(arr: T[]): string { // return arr.map(elt => hashCode(elt.hash())).reduce((pv, cv) => 31 * pv + cv).hash(); // } //