Skip to content

Instantly share code, notes, and snippets.

@syxolk
Created June 2, 2022 23:33
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 syxolk/689e9ef5564850311ce4f522c8c7c429 to your computer and use it in GitHub Desktop.
Save syxolk/689e9ef5564850311ce4f522c8c7c429 to your computer and use it in GitHub Desktop.
Generic JS Map for complex key types
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