Skip to content

Instantly share code, notes, and snippets.

@trvswgnr
Last active September 21, 2023 16:41
Show Gist options
  • Save trvswgnr/a673b45bfecd600e7f95dc1009f87127 to your computer and use it in GitHub Desktop.
Save trvswgnr/a673b45bfecd600e7f95dc1009f87127 to your computer and use it in GitHub Desktop.
triple key map, where the order of keys doesn't matter
export class TripMap<K, V> {
private map: Map<number, V>;
constructor() {
this.map = new Map<number, V>();
}
private generateKey(...items: any[]): number {
return [...new Set(items)]
.map((x) => this.hash(x))
.sort()
.map((x, i) => x + i + 1)
.reduce((p, c) => p ^ c, 0);
}
private hash(item: any): number {
let hash = 0;
let str: string;
if (typeof item === "object" && item !== null) {
str = "";
const keys = Object.keys(item).sort();
for (const key of keys) {
str += key + this.hash(item[key]); // recursive call for nested objects
}
} else if (Array.isArray(item)) {
str = "";
for (let i = 0; i < item.length; i++) {
str += this.hash(item[i]); // recursive call for array elements
}
} else {
str = String(item);
}
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0; // convert to 32bit integer
}
return hash;
}
set(item1: K, item2: K, item3: K, value: V): void {
const key = this.generateKey(item1, item2, item3);
this.map.set(key, value);
}
get(item1: K, item2: K, item3: K): V | undefined {
const key = this.generateKey(item1, item2, item3);
return this.map.get(key);
}
delete(item1: K, item2: K, item3: K): void {
const key = this.generateKey(item1, item2, item3);
this.map.delete(key);
}
}
import { describe, test, expect } from 'bun:test';
import { TripMap } from './index';
describe('TripMap', () => {
test('passes regular tests', () => {
const tripMap = new TripMap<any, number>();
// add entries
tripMap.set("apple", "banana", "cherry", 10);
tripMap.set("dog", "cat", "mouse", 20);
tripMap.set("red", "green", "blue", 30);
// get values
expect(tripMap.get("banana", "cherry", "apple")).toBe(10);
expect(tripMap.get("cat", "mouse", "dog")).toBe(20);
expect(tripMap.get("green", "blue", "red")).toBe(30);
// delete entries
tripMap.delete("cherry", "apple", "banana");
// get values after deletion
expect(tripMap.get("banana", "cherry", "apple")).toBe(undefined);
// add entries with same items
tripMap.set("apple", "apple", "apple", 40);
tripMap.set("brapple", "brapple", "brapple", 50);
// get values with same items
expect(tripMap.get("apple", "apple", "apple")).toBe(40);
// obj with diff order keys
tripMap.set({ a: 1, b: 2 }, "banana", "cherry", 10);
expect(tripMap.get("cherry", { b: 2, a: 1 }, "banana")).toBe(10);
tripMap.set(1, 2, 3, 10);
expect(tripMap.get(2, 2, 2)).toBe(undefined);
});
test('passes edge cases', () => {
const tripMap = new TripMap<any, number>();
// 0 with 2 other values
tripMap.set(0, 1, 1, 69);
tripMap.set(0, 2, 2, 420);
expect(tripMap.get(0, 1, 1)).toBe(69);
tripMap.set(3, 5, 5, 99);
tripMap.set(3, 9, 9, 100);
expect(tripMap.get(3, 5, 5)).toBe(99);
expect(tripMap.get(0, 1, 1)).toBe(69);
tripMap.set(0, 5, 5, 1);
tripMap.set(0, 6, 6, 2);
expect(tripMap.get(0, 5, 5)).toBe(1);
expect(tripMap.get(0, 6, 6)).toBe(2);
expect(tripMap.get(0, 2, 2)).toBe(420);
// empty map
expect(tripMap.get("banana", "cherry", "apple")).toBe(undefined);
expect(tripMap.get("cat", "mouse", "dog")).toBe(undefined);
expect(tripMap.get("green", "blue", "red")).toBe(undefined);
// empty objects
let res = Math.random();
tripMap.set({}, {}, {}, res);
expect(tripMap.get({}, {}, {})).toBe(res);
// empty items
tripMap.set("", "", "", 10);
expect(tripMap.get("", "", "")).toBe(10);
// undefined items
tripMap.set(undefined, undefined, undefined, 20);
expect(tripMap.get(undefined, undefined, undefined)).toBe(20);
// null items
tripMap.set(null, null, null, 30);
expect(tripMap.get(null, null, null)).toBe(30);
// NaN items
tripMap.set(NaN, NaN, NaN, 40);
expect(tripMap.get(NaN, NaN, NaN)).toBe(40);
// 0 items
tripMap.set(0, 0, 0, 50);
expect(tripMap.get(0, 0, 0)).toBe(50);
// false items
tripMap.set(false, false, false, 60);
expect(tripMap.get(false, false, false)).toBe(60);
// true items
tripMap.set(true, true, true, 70);
expect(tripMap.get(true, true, true)).toBe(70);
// array items
tripMap.set([1, 2, 3], [4, 5, 6], [7, 8, 9], 80);
expect(tripMap.get([1, 2, 3], [4, 5, 6], [7, 8, 9])).toBe(80);
// object items
tripMap.set({ a: 1, b: 2 }, { c: 3, d: 4 }, { e: 5, f: 6 }, 90);
expect(tripMap.get({ a: 1, b: 2 }, { c: 3, d: 4 }, { e: 5, f: 6 })).toBe(90);
// mixed items
tripMap.set("apple", 0, false, 100);
expect(tripMap.get("apple", 0, false)).toBe(100);
});
test('passes random number tests', () => {
const tripMap = new TripMap<number, number>();
for (let i = 0; i < 1000; i++) {
const item1 = Math.random();
const item2 = Math.random();
const item3 = Math.random();
const value = Math.random();
tripMap.set(item1, item2, item3, value);
expect(tripMap.get(item1, item2, item3)).toBe(value);
}
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment