Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any nice way to groupBy while transforming values with Lo-Dash 3.0?

Given an array of objects like this:

var data = [
    {key: 'a', val: '1'}, 
    {key: 'a', val: '2'}, 
    {key: 'b', val: '3'}, 
    {key: 'c', val: '4'}, 
    {key: 'c', val: '5'}, 
    {key: 'c', val: '6'}
];

I would like to convert it to this:

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

I've found two ways to achieve this so far with lodash-fp, but I'm still wondering if there's a better way.

The first way is somewhat procedural:

var out = _(data)
    .transform(function(out, item) {
        out[item.key] = out[item.key] || [];
        out[item.key].push(item.val);
    }, {});

The second way is in the point-free style I was hoping to achieve:

var out = _(data)
    .groupBy(_.property('key'))
    .mapValues(_.map(_.property('val')))
    .value();
// Yes, I know that _.property is implied if I just pass a string

However, this is more cluttered than I'd like: I have to iterate over the intermediate results to transform the grouped values, and I think it obscures what the code is trying to accomplish. I can't transform first, though, as the transformation I want removes the keys!

Is there anything like a groupByTransforming(groupIteratee, transformIteratee) method?

like image 349
mmebane Avatar asked Jan 30 '15 19:01

mmebane


1 Answers

I don't know anything about LoDash (sorry), but I have a simple function that will do what you want that just uses vanilla JS:

/**
 * Maps an array of objects into a single object,
 * grouped by one property and supplying another.
 * @param  {Array}  input     The array of objects containg data
 * @param  {String} groupBy   The name of the property to group the elements by
 * @param  {String} groupProp The property to push into each array
 * @return {Object}           The mapped object.
 */
function mapToObject(input, groupBy, groupProp) {

    var obj = {};

    // Loop through the data
    input.forEach(function (data) {
        // If the output object doesn't contain the key,
        // make it as an empty array
        if (!obj[data[groupBy]]) {
            obj[data[groupBy]] = [];
        };
        // Push the value into the obj[groupBy] array
        obj[data[groupBy]].push(data[groupProp]); 
    });

    return obj;

}

In your case you would use it like this:

mapToObject(data, 'key', 'val'), so it'd return an Object of the data grouped by 'key' and with the values of 'val'. See the snippet below for an example:

var data = [
    {key: 'a', val: '1'}, 
    {key: 'a', val: '2'}, 
    {key: 'b', val: '3'}, 
    {key: 'c', val: '4'}, 
    {key: 'c', val: '5'}, 
    {key: 'c', val: '6'}
];

/**
 * Maps an array of objects into a single object,
 * grouped by one property and supplying another.
 * @param  {Array}  input     The array of objects containg data
 * @param  {String} groupBy   The name of the property to group the elements by
 * @param  {String} groupProp The property to push into each array
 * @return {Object}           The mapped object.
 */
function mapToObject(input, groupBy, groupProp) {

	var obj = {};

	// Loop through the data
	input.forEach(function (data) {
		// If the output object doesn't contain the key,
		// make it as an empty array
		if (!obj[data[groupBy]]) {
			obj[data[groupBy]] = [];
		};
		// Push the value into the obj[groupBy] array
		obj[data[groupBy]].push(data[groupProp]); 
	});

	return obj;

}

// Just to show example output:
document.write(JSON.stringify(mapToObject(data, 'key', 'val')));
like image 88
Mental_Atom Avatar answered Oct 18 '22 20:10

Mental_Atom