Edit 24/12/2022
I have created an ES module for order agnostic multi map. I will explain here how you can set it up for the OP's use case.
https://github.com/martian17/ds-js
First you will want to clone the repository to your project, or copy the code.
$ git clone https://github.com/martian17/ds-js.git
Here is an example use case
// import it to your project
import {OrderAgnosticMultiMap} from "path_to_ds-js/multimap.mjs";
// Instantiate
const map = new OrderAgnosticMultiMap();
// Register values
map.set("keyA", "keyB", "keyC", "content 1");
map.set("keyA", "keyC", "keyC", "content 2");
map.set("keyA", "keyB", "keyB", "content 3");
// The keys can be any object
map.set(map, OrderAgnosticMultiMap, map, window, document, "content 4");
// Get values (keys can be in different orders)
console.log(map.get("keyB", "keyC", "keyA"));
// log: "content 1"
console.log(map.get("keyB", "keyB", "keyC"));
// log: undefined
map.get(document, map, window, OrderAgnosticMultiMap, map);
// log: "content 4"
// Check if a value exists for some keys
console.log(map.has("keyC", "keyC", "keyA"));
// log: true
console.log(map.has("keyA", "keyC", "keyA"));
// log: false
// Loop through values
for(let [tally,value] of map){
console.log(tally,value);
}
// log:
// Map(3) {"keyA" => 1, "keyB" => 1, "keyC" => 1} 'content 1'
// Map(3) {"keyA" => 1, "keyC" => 2} 'content 2'
// Map(3) {"keyA" => 1, "keyB" => 2} 'content 3'
// Map(3) {map => 2, OrderAgnosticMultiMap => 1, window => 1, document => 1} 'content 4'
// Delete keys
map.delete("keyC", "keyB", "keyA");
map.delete("keyB", "keyB", "keyA");
map.delete("keyC", "keyC", "keyA");
console.log(map.has("keyC", "keyC", "keyA"));
// log: false
Pre-edit
If there is anyone wondering if there is a solution for multi keyed ES6 map, here's my take.
The order does matter though, so map.get(a,b,c) and map.get(c,a,b) will fetch different values.
And you can of course use this as string to object map, so it satisfies the OP's use case as well.
class MultiMap{
map = new Map;
own = Symbol();// unique value that doesn't collide
set(){
let lst = [...arguments];
let val = lst.pop();
let map = this.map;
for(let k of lst){
if(!map.has(k))map.set(k,new Map);
map = map.get(k);
}
map.set(this.own,val);// to avoid collision between the same level
return val;
}
get(...lst){
let map = this.map;
for(let k of lst){
if(!map.has(k))return undefined;
map = map.get(k);
}
return map.get(this.own);
}
has(...lst){
let map = this.map;
for(let k of lst){
if(!map.has(k))return false;
map = map.get(k);
}
return map.has(this.own);
}
delete(...lst){
let map = this.map;
let maps = [[null,map]];
for(let k of lst){
if(!map.has(k))return false;
map = map.get(k);
maps.push([k,map]);
}
let ret = map.delete(this.own);
for(let i = maps.length-1; i > 0; i--){
if(maps[i][1].size === 0){
maps[i-1][1].delete(maps[i][0]);
}else{
break;
}
}
return ret;
}
}
Example use case
let a = {a:"a"};
let b = {b:"b"};
let c = {c:"c"};
let mm = new MultiMap;
//basic operations
console.log(mm.set(a,b,c,"abc"));// "abc"
console.log(mm.get(a,b,c));// "abc"
console.log(mm.has(a,b,c));// true
console.log(mm.delete(a,b,c));// true
// overlapping keys can be handled fine as well
mm.set(a,b,"ab");
mm.set(a,"a");
console.log(mm.get(a,b));// "ab"
console.log(mm.get(a));// "a"
For anyone curious about my use case: I was trying to make an event listener wrapper that maps to multiple events internally (mousedown => mousedown, touchstart etc). I needed to cache the arguments when .on() is called so .off() can find the right set of event listeners to remove.