Last active
September 21, 2023 16:41
-
-
Save trvswgnr/a673b45bfecd600e7f95dc1009f87127 to your computer and use it in GitHub Desktop.
triple key map, where the order of keys doesn't matter
This file contains hidden or 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
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); | |
} | |
} |
This file contains hidden or 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
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