How to transpose object in underscorejs
Asked Answered
R

3

9

In JavaScript I am trying to convert an array of objects with similar keys:

[{'a':1,'b':2}, {'a':3,'b':4}, {'a':5,'b':6,'c':7}]

to an object with an array of values for each key:

{'a':[1,3,5], 'b':[2,4,6], 'c':[7]};

using underscore.js 1.4.2.

I have some working code below, but it feels longer and clunkier than just writing nested for loops.

Is there a more elegant way of doing this in underscore? Is there something simple I'm missing?

console.clear();

var input = [{'a':1,'b':2},{'a':3,'b':4},{'a':5,'b':6,'c':7}];
var expected = {'a':[1,3,5], 'b':[2,4,6], 'c':[7]};

// Ok, go
var output = _(input)
.chain()
// Get all object keys
.reduce(function(memo, obj) {
    return memo.concat(_.keys(obj));
}, [])
// Get distinct object keys
.uniq()
// Get object key, values
.map(function(key) {
    // Combine key value variables to an object  
    // ([key],[[value,value]]) -> {key: [value,value]}
    return _.object(key,[
        _(input)
        .chain()
        // Get this key's values
        .pluck(key)
        // Filter out undefined
        .compact()
        .value()
    ]);
})
// Flatten array of objects to a single object
// [{key1: [value]}, {key2, [values]}] -> {key1: [values], key2: [values]}
.reduce(function(memo, obj) {
    return _.extend(memo, obj);
}, {})
.value();

console.log(output);
console.log(expected);
console.log(_.isEqual(output, expected));

Thanks

Rok answered 13/11, 2012 at 0:4 Comment(0)
B
8

Sounds like you want zip for objects. This would be the analogous method for objects:

_.transpose = function(array) {
    var keys = _.union.apply(_, _.map(array, _.keys)),
        result = {};
    for (var i=0, l=keys.length; i<l; i++) {
        var key = keys[i];
        result[key] = _.pluck(array, key);
    }
    return result;
};

However, I would just use

_.transpose = function(array) {
    var result = {};
    for (var i=0, l=array.length; i<l)
        for (var prop in array[i]) 
             if (prop in result)
                 result[prop].push(array[i][prop]);
             else
                 result[prop] = [ array[i][prop] ];
    return result;
};

without any Underscore at all :-) Of course, you could use some iterator methods, it then might look like

_.reduce(array, function(map, obj) {
    return _.reduce(obj, function(map, val, key) {
        if (key in map)
            map[key].push(val)
        else
            map[key] = [val];
        return map;
    }, map);
}, {});
Bonar answered 13/11, 2012 at 0:36 Comment(3)
@Bergi: Thanks thats pretty comprehensive. Do you mind if I open a ticket on underscore github to include a transpose function and use your answer as an example?Rok
Oh and the nested reduce is what I was trying to write I think.Rok
@MarkC: No, I never mind being credited :-) Still trying to solve this problem for arbitrary nestings of arrays and objects…Bonar
U
1

You can use lodash's zipObject mehtod: https://lodash.com/docs#zipObject

Unscratched answered 8/3, 2015 at 19:15 Comment(0)
S
0

You need 3 lines of lodash:

_.merge.apply(null, _.union([{}], myArrayOfObjects, [function (a, b) {
    return _.compact(_.flatten([a, b]));
}]))

See the docs of _.merge for more details on what the function does.

Spode answered 3/9, 2015 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.