I'm trying to use the new (ES6) Map
objects in order to represent a map between properties and a value.
I have objects in a form similar to:
{key1:value1_1,key2:value2_1},..... {key1:value1_N,key2:value2_N}
I want to group them based on both their key1 and key2 value.
For example, I want to be able to group the following by x
and y
:
[{x:3,y:5,z:3},{x:3,y:4,z:4},{x:3,y:4,z:7},{x:3,y:1,z:1},{x:3,y:5,z:4}]
And obtain a Map containing:
{x:3,y:5} ==> {x:3,y:5,z:3},{x:3,y:5,z:4}
{x:3,y:4} ==> {x:3,y:4,z:4},{x:3,y:4,z:7}
{x:3,y:1} ==> {x:3,y:1,z:1}
In Python, I'd use tuples as dictionary keys. ES6 map allow arbitrary objects as keys but use the standard equality algorithm (===
) so objects are only equal by reference from what I can tell.
How can I accomplish this sort of grouping using ES6 maps? Alternatively, a solution using normal JS objects if there is an elegant way I overlooked.
I'd rather not use an external collections library - but if there is a better solution using one I'm interested in learning about it too.
Ok, I've raised the issue on esdiscuss now and I got an answer from Mozilla's Jason Orendorff:
.equals
and.hashCode
but it was rejected in favor of value objects. (for good reasons in my opinion).A basic such collection (concept, don't use in production code) was offered by Bradley on the ESDiscuss thread and might look something like this:
A better solution is to use something like MontageJS/Collections which allows for specification of hash/equals functions.
You can see the API docs here.
Benjamin's answer doesn't work for all objects, as it relies on JSON.stringify, which cannot handle circular objects and can map different objects to the same string. Minitech's answer can create huge trees of nested maps, which I suspect are both memory and CPU inefficient, especially for long tuples, as it has to create a map for each element in the tuple.
If you know that your tuples only contain numbers, then the best solution is to use
[x,y].join(',')
as the key. If you want to use tuples containing arbitrary objects as keys, you can still use this method, but have to map the objects to unique identifiers first. In the code below I generate these identifiers lazily usingget_object_id
, which stores the generated ids in an internal map. I can then generate keys for tuples by concatenating those ids. (See code at the bottom of this answer.)The
tuple
method can then be used to hash tuples of objects to a string that can be used as the key in a map. This uses object equivalence:If you're certain that your tuples will only contain objects (i.e. not null, numbers or strings), then you can use a WeakMap in
get_object_id
, such thatget_object_id
andtuple
won't leak objects that are passed as argument to them.While this question is quite old, value objects are still not an existing thing in JavaScript (so people might still be interested) so I decided to write a simple library to accomplish similar behaviour for arrays as keys in maps (repo here: https://github.com/Jamesernator/es6-array-map). The library is designed to be basically identical to map in usage except arrays are compared element-wise instead of by identity.
Usage:
Warning: The library however treats key elements by identity so the following doesn't work:
But as long as you can serialize the data into arrays of primitives you can use ArrayMap as follows:
It doesn’t seem conveniently possible. What can you do? Something horrible, as always.
And voilà.