1

How would you do this kind of transformation in a functional way with underscore.js? I've repeated the following pattern several times lately, and it seems kind of ugly to me.

var fruitList = [
        { id:1, name:lime, color:green, size:small },
        { id:2, name:banana, color:yellow, size:large },
        { id:3, name:lemon, color:yellow, size:small },
        { id:4, name:papaya, color:green, size:large },
        { id:5, name:kiwi, color:green, size:small },
        { id:6, name:apple, color:green, size:small },
        { id:7, name:pear, color:yellow, size:small },
        { id:8, name:grape, color:green, size:small },
        { id:9, name:mango, color:yellow, size:large },
        { id:10, name:honeydew, color:green, size:large }
    ],
    fruitByColorThenSize = {};

jQuery.each(fruitList, function (fruit) {
    if (!fruitByColorThenSize[fruit.color]) {
        fruitByColorThenSize[fruit.color] = {};
    }

    if (!fruitByColorThenSize[fruit.color][fruit.size]) {
        fruitByColorThenSize[fruit.color][fruit.size] = {};
    }

    fruitByColorThenSize[fruit.color][fruit.size][fruit.id] = fruit.name;
});

fruitByColorThenSize would then look similar to this:

{
    yellow: {
        large: {
            2: 'banana',
            9: 'mango'
        },
        small: {
            3: 'lemon',
            7: 'pear'
        }
    },
    green: {
        large: {
            4: 'papaya',
            10: 'honeydew'
        },
        small: {
            1: 'lime',
            5: 'kiwi',
            6: 'apple',
            8: 'grape'
        }
    }
}

1 Answer 1

2

There's a nice function called groupBy which does exactly what you want, yet for a one-dimensional object only. In this answer I've written a generic multidimensional version of it, but for your case the following functional script is enough:

var fruitList = […];

var fruitByColorThenSize = _.groupBy(fruitList, "color");
for (var color in fruitByColorThenSize) {
    var byColor = _.groupBy(fruitByColorThenSize[color], "size");
    for (var size in byColor) {
        var bySize = _.groupBy(byColor[size], "id");
        for (var id in bySize) {
            bySize[id] = bySize[0].name;
        }
        byColor[size] = bySize;
    }
    fruitByColorThenSize[color] = byColor;
}

I've repeated the following pattern several times lately

Then write a generic function abstracting over it! It's quite simple:

function getBy(arr, valprop, props) {
    var res = {};
    $.each(arr, function(obj) {
        var acc = res;
        for (var i=0; i<props.length; i++) {
            var prop = obj[props[i]];
            if (i==props.length-1) 
                acc[prop] = obj[valprop];
            else {
                if (! (prop in acc))
                    acc[prop] = {};
                acc = acc[prop];
            }
        }
    });
    return res;
}
// usage:
var fruitByColorThenSize = getBy(fruitList, "name", ["color", "size", "id"]);
Sign up to request clarification or add additional context in comments.

3 Comments

I'm getting an array using your solution, instead of an object.
Yes, this gets part of the way there. @Bergi's code generates an array of arrays of objects, while I was looking for an object of objects of objects. I will check out the linked multidimensional answer more closely tomorrow. Also, the link there to underscore.nest looks cool. Thanks!
@idbehold: Oops, I though Underscore's map on objects would return an object. That will make the answer less functional :-(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.