Background
lodash and underscore have an invert
function that takes an object hash and converts it to a new one, which has keys as the input object's values and values as the input object's keys. As such, if the values in the input object aren't unique, this non-unique input value will, when it's a key in the output object, have as its value only one of the input object's keys.
An example:
_.invert({a: 1, b: 2, c: 2})
// { 1 : "a", 2 : "c" }
The twist
I frequently work with object hashes whose values are one-dimensional arrays (vectors). I've written a function using lodash/underscore (NB. only tested with lodash) that performs an array-aware invert
, whose output object has keys that are the unique elements of the input object's value vectors, and values that are the input object's keys.
function arrayAwareInvert(obj) {
return _.object(_.flatten(_.map(obj, function(valVec, key) {
return valVec.map(function(val) { return [ val, key ]; });
})));
}
I'd appreciate feedback on this function.
(NB. I can't use lodash/underscore's built-in invert
with an object hash with array-valued keys because the resulting object has keys which simply stringify the arrays—an output object key might be [1, 2, 3].toString()
, and entirely useless.)
Example use
Just to confirm that it works:
arrayAwareInvert({a: [1, 2], b: [3, 4]})
// { 1: "a", 2: "a", 3: "b", 4: "b" }
Per the original invert
's behavior, if the elements of the input object's value vectors aren't unique (that is, if multiple value vectors contain the same element), the output object's value for those non-unique elements will be one of the input object keys:
arrayAwareInvert({a: [1, 2], b: [3, 4, 2]})
// { 1: "a", 2: "b", 3: "b", 4: "b" } // NB: 2: "b" here
Implementation notes
My implementation seems as brute-force as possible: it's effectively a doubly-nested loop, with the outer loop going over all input object keys and the inner loop going over the contents of each input object value (which is a vector).
A [key, value]
tuple is built for each iteration of the inner-most loop, where "key"/"value" refer to the output object.
flatten
is used to remove one level of nesting, i.e., transform [[[k1, v1]], [[k2, v2], [k3, v3]]]
to [[k1, v1], [k2, v2], [k3, v3]]
.
Finally object
is called to convert this list of 2-tuple key-value pairs into an object hash.
Summary
Are there implementations that occupy more advantageous positions in the clarity-speed-elegance phase space?
Does this operation have a more general name?
Analyses of solutions
reduce
I can simplify? @Flambino's solution a bit with _.merge
:
function arrayAwareInvert(obj) {
return _.reduce(obj, function (result, values, key) {
return _.merge(result,
_.mapValues(_.object(values), function(v) { return key; }));
}, {});
}
I like using merge
here because it describes what's happening very well. But I don't really like how much code is needed to combine a vector and a string into an object hash with keys as elements of the vector and values as the string:
_.mapValues(_.object(values), function(v) { return key; })
As one (of many) alternatives, you could do this instead:
_.object(values, values.map(function(v) { return key; }))
but both of these seem obfuscated, compared with how clear the reduce
and merge
steps are.
invert
to attempt to answer your question. In more general terms, both libraries offerinvert
without a straightforward way to generalize them to array-valued objects, so I tagged them both. Since my implementation uses functions available in both, I feel this is ok. \$\endgroup\$