18

I have 3 dropdowns on my page, the options in the 3rd dropdown depend on the choices in the first two dropdowns.

So I am wondering if there is a way to implement a map in javaScript with a 2-dimensional key? Like <Key1, Key2> -> Value.

I think an easy way is to concatenate two keys into one string. Is there any way that is more decent?

Thanks.

Colin Brock
  • 21,267
  • 9
  • 46
  • 61
nababa
  • 1,251
  • 3
  • 13
  • 20
  • 2
    If you've got keys as strings, and can verify that they don't contain specific characters, such as `|`, you could use something like `map[a + '|' + b]` – zzzzBov Dec 21 '11 at 20:04
  • JavaScript doesn't offer a way to do this easily, so I wrote [ManyKeysMap](https://github.com/fregante/many-keys-map) and [ManyKeysWeakMap](https://github.com/fregante/many-keys-weakmap) to extend a regular `Map`/`WeakMap` to accept any number and types of keys. – fregante Oct 10 '20 at 20:04

9 Answers9

5

You could have an object that contains more objects:

var options = {
    'option 1': {
        'option 1.1': [
            'option 1.1.1',
            'option 1.1.2',
            'option 1.1.3',
            'option 1.1.4'
        ],
        'option 1.2': [
            'option 1.2.1',
            /* etc. */
};

Then, you would access the options for the third dropdown as options[firstSelectedValue][secondSelectedValue].


EDIT: Here's a demo, too, using some new features that you may need to implement if you're browsing using Internet Explorer 8 or lower :)

Ry-
  • 218,210
  • 55
  • 464
  • 476
3

What is wrong with concatenating the two keys? All you need is to be sure, that the concatenated keys are unique, and I guess that this can easily be achieved by separating the two values with a character that is not used by any of the two keys.

Jan Aagaard
  • 10,940
  • 8
  • 45
  • 80
  • 2
    You can only concatenate strings, not Objects. If one key is an Element and the other one is a Function, you can't. – fregante Oct 10 '20 at 20:06
  • If you allow any value in both strings, then you have no separator character and this would not work. – xmedeko Jun 08 '23 at 09:51
2

I was looking for a similar data structure, but could not find. I have been working with TypeScript, so I have developed a solution using TypeScript.

export class TwoMapKey<T> {
    private __map__: object;

    constructor() {
        this.__map__ = new Object();
        this.get = this.get.bind(this);
        this.set = this.set.bind(this);
        this.remove = this.remove.bind(this);
        this.keys = this.keys.bind(this);
        this.nestedKeys = this.nestedKeys.bind(this);
        this.clear = this.clear.bind(this);
    }

    public get(key: string, nestedKey: string): T {
        if (!this.__map__[key] || this.__map__[key] && !this.__map__[key][nestedKey])
            return;

        return this.__map__[key][nestedKey];
    }

    public set(key: string, nestedKey: string, value: T): void {
        if (!this.__map__[key]) {
            this.__map__[key] = new Object();
        }

        Object.defineProperty(this.__map__[key], nestedKey, { value: value, configurable: true, enumerable: true });
    }

    public remove(key, nestedKey): void {
        if (!this.__map__[key]) {
            return;
        }

        delete this.__map__[key][nestedKey];
    }

    public keys(): string[] {
        return Object.getOwnPropertyNames(this.__map__);
    }

    public nestedKeys(): Array<string[]> {
        return Object.getOwnPropertyNames(this.__map__).map(key => Object.keys(this.__map__[key]));
    }

    public clear(): void {
        Object.getOwnPropertyNames(this.__map__).forEach(property => {
            delete this.__map__[property];
        });
    } }

You can simply create a map with two keys as below:

let twoKeyMap = new TwoKeyMap<any>();
twoKeyMap.set('mainKey1', 'nestedKey1', {value: 'value'});

Its not as efficent as a HashMap. You should be able to convert the same in ES6 or ES5 easily.

Rahul Sethi
  • 349
  • 2
  • 5
2

You can set a key as an array. Just create your array [key1, key2]; Then set that value as your key and relate it to your value.

obj[[key1,key2]] = "my value";

Here is a jsFiddle http://jsfiddle.net/TwQLW/

Lokesh Pandey
  • 1,739
  • 23
  • 50
Keith.Abramo
  • 6,952
  • 2
  • 32
  • 46
  • 18
    Object properties *must* be strings, so any other values passed are converted to a string. This makes this answer the same as concatenating the two strings because arrays are automatically joined with `,`. – Andy E Dec 21 '11 at 23:05
  • Thanks @Keith.Abramo. – Ankit Ostwal Sep 27 '16 at 05:57
1

I created a generic data structure for this purpose as I had a similar problem:

https://github.com/vikashmadhow/map

I know quite some time has passed since this question was asked; hopefully this might be of help to others.

Vikash Madhow
  • 1,287
  • 11
  • 15
1

You can achieve this by adding the two keys as a key array on a hash object using ES6 syntax. Then you can search using array filter and regular expression:

const key1 = "key1";
const key2 = "key2";
const value = "whatever";

const hashObject = {};
const firstEntry = [key1, key2];
const secondEntry = [key2, key1];
const entryValue = { value };

hashObject[firstEntry] = entryValue;
hashObject[secondEntry] = entryValue;

const regByFirstKey = new RegExp(`${key1},.`);
const regBySecondKey = new RegExp(`.,${key2}`);

const keysArray = Object.keys(hashObject);

const resultByFirstKey = (keysArray.filter((key) => regByFirstKey.test(key)))[0];
const resultBySecondKey = (keysArray.filter((key) => regBySecondKey.test(key)))[0];

resultByFirstKey ? console.log(hashObject[resultByFirstKey].value) : undefined;
resultBySecondKey ? console.log(hashObject[resultBySecondKey].value) : undefined;
Itay Merchav
  • 954
  • 8
  • 8
0

A Map with two keys could be thought of as a graph, where the two keys are nodes with an edge between them.

class Graph {
  nodes = new Map();

  addNode(ref) {
    if (!this.nodes.has(ref)) {
      this.nodes.set(ref, new Map());
    }
  }

  addEdge(ref1, ref2, value) {
    this.nodes.get(ref1)?.set(ref2, value);
    this.nodes.get(ref2)?.set(ref1, value);
  }

  getEdge(ref1, ref2) {
    return this.nodes.get(ref1)?.get(ref2);
  }
}

const graph = new Graph();

graph.addNode('left');
graph.addNode('right');
graph.addEdge('left', 'right', 12345);

console.log(graph.getEdge('left', 'right')); // 12345
console.log(graph.getEdge('right', 'left')); // 12345

You could even make it simpler by creating non-existent nodes in the addEdge method itself.

Joe Maffei
  • 1,855
  • 1
  • 15
  • 18
0

There is no other way. JS map is plain object, so the key the same as property name. Even if you try to use eg. array as a key, it'll be converted to string. You should concat this string with some special char between or store values in array, and scan it on every access.

Krzysztof
  • 15,900
  • 2
  • 46
  • 76
-4
/**
 * type Keys = 'key1' | 'key2';
 * const twoKeysMap = new Map<Keys, number>();    
 */
const twoKeysMap = new Map<'key1' | 'key2', number>();

twoKeysMap.set('key1', 1); // -> ok
twoKeysMap.set('key2', 2); // -> ok
twoKeysMap.set('key3', 3); // -> error
zemil
  • 3,235
  • 2
  • 24
  • 33