Created
June 2, 2022 23:33
-
-
Save syxolk/689e9ef5564850311ce4f522c8c7c429 to your computer and use it in GitHub Desktop.
Generic JS Map for complex key types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class MapOfComplexData< | |
Key, | |
Value, | |
HashFn extends (key: Key) => number = (key: Key) => number, | |
EqualsFn extends (objA: Key, objB: Key) => boolean = (objA: Key, objB: Key) => boolean | |
> { | |
private _data = new Map<number, {key: Key; value: Value}[]>(); | |
private _size: number = 0; | |
private hashCode: HashFn; | |
private equals: EqualsFn; | |
constructor({hashCode, equals}: { | |
hashCode: HashFn; | |
equals: EqualsFn | |
}) { | |
this.hashCode = hashCode; | |
this.equals = equals; | |
} | |
public set(key: Key, value: Value) { | |
const hash = this.hashCode(key); | |
let list = this._data.get(hash); | |
if (list === undefined) { | |
list = []; | |
this._data.set(hash, list); | |
} | |
for(const item of list) { | |
if(this.equals(item.key, key)) { | |
item.value = value; | |
return; | |
} | |
} | |
// fallback in case the key was not found | |
list.push({key, value}); | |
this._size++; | |
} | |
public get(key: Key): Value | undefined { | |
const hash = this.hashCode(key); | |
const list = this._data.get(hash); | |
if (list === undefined) { | |
return undefined; | |
} | |
for (const item of list) { | |
if (this.equals(item.key, key)) { | |
return item.value; | |
} | |
} | |
return undefined; | |
} | |
public clear(): void { | |
this._data.clear(); | |
this._size = 0; | |
} | |
public delete(key: Key) { | |
const hash = this.hashCode(key); | |
const list = this._data.get(hash); | |
if (list === undefined) { | |
return; | |
} | |
for (const [index, item] of list.entries()) { | |
if (this.equals(item.key, key)) { | |
list.splice(index, 1); | |
this._size--; | |
if (list.length === 0) { | |
this._data.delete(hash); | |
} | |
return; | |
} | |
} | |
} | |
public* entries(): Generator<[Key, Value]> { | |
for (const list of this._data.values()) { | |
for (const {key, value} of list) { | |
yield [key, value]; | |
} | |
} | |
} | |
public* values(): Generator<Value> { | |
for (const [_, value] of this.entries()) { | |
yield value; | |
} | |
} | |
public* keys(): Generator<Key> { | |
for (const [key, _] of this.entries()) { | |
yield key; | |
} | |
} | |
public get size(): number { | |
return this._size; | |
} | |
public has(key: Key): boolean { | |
const hash = this.hashCode(key); | |
const list = this._data.get(hash); | |
if (list === undefined) { | |
return false; | |
} | |
for (const item of list) { | |
if (this.equals(item.key, key)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
public forEach(callback: (value: Value, key: Key, map: MapOnComplexData<Key, Value>) => void) { | |
for (const list of this._data.values()) { | |
for (const item of list) { | |
callback(item.value, item.key, this); | |
} | |
} | |
} | |
} | |
const map = new MapOnComplexData<number[], string>({ | |
equals(a, b) { | |
if (a.length !== b.length) { | |
return false; | |
} | |
for (let i=0; i<a.length; i++) { | |
if (a[i] !== b[i]) { | |
return false; | |
} | |
} | |
return true; | |
}, | |
hashCode(obj) { | |
return obj.reduce((a,b) => a^b, 0); | |
} | |
}); | |
map.set([1, 2, 3], "foo"); | |
map.set([1, 2, 3], "foo2"); | |
map.set([4, 5, 6], "bla"); | |
console.log(map.get([1, 2, 3])); | |
console.log(map.size); | |
console.log(map.has([4,5,6])); | |
console.log(map.has([1,2,3])); | |
console.log(map.has([7,8,9])); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment