Underscore – _.reduce()

On March 23, 2012, in JavaScript, by Jim Cowart

It’s definitely no secret that I’m a fan of underscore.js. I’ve decided to write a collection of quick posts to highlight interesting ways in which underscore has proven useful. For today, let’s look at the “reduce” function.

From underscore’s documentation, reduce has the following signature:
_.reduce(list, iterator, memo, [context])

The docs describe it this way: “reduce boils down a list of values into a single value.  Memo is the initial state of the reduction, and each successive step of it should be returned by iterator“.

If you come away from that description thinking that reduce would be a powerful tool in working with numeric data – you are correct (in fact, underscore’s example presents it as such).  However, in a recent project, I ran into a situation where reduce made crawling an object literal a very simple task.  In this scenario, I had an object literal that provided state information for several components of the application.  Here’s a snippet of what the object looked like (changed & simplified, of course, to not reflect real data):

var info = {
    item1: {
        isRemote: false,
        active: false
        /* all kinds of other information for item1, etc. */
    },
    item2: {
        isRemote: false,
        active: false
        /* all kinds of other information for item2, etc. */
    },
    item3: {
        isRemote: false,
        active: false
        /* all kinds of other information for item3, etc. */
    },
    item4: {
        isRemote: false,
        active: false
        /* all kinds of other information for item4, etc. */
    }
};​​​​​​​​​​​​

This particular application also had an object that grouped the members of the info object together in various ways. For example:

var groups {
    group1: ["item1", "item2", "item4"],
    group2: ["item1", "item2", "item7"],
    group3: ["item1", "item3", "item4", "item6"],
    group4: ["item2", "item3", "item5"],
    group5: ["item2", "item3", "item4"],
};

Only one group of items would be active at a time. I wanted a clean way to switch between groups – effectively setting active = false for any item that didn’t belong in the new group being activated, but if an item was already active, and existed in the new group being activated, leave it alone. Below is an example of how you can use underscore’s reduce (in tandem with “each” and “difference”) to quickly produce the list of items that should be deactivated, etc.:

var activateGroup = function(groupName) {
    // get the items for this group
    var items = groups[groupName];
    // find out which items are active that aren't in the group we're activating
    var shouldDeActivate = _.difference(
        _.reduce(info, function(memo, val, key) {
            if (val.active) {
                memo.push(key);
            }
            return memo;
        }, [])
    ,items);

    // iterate over what we should deactivate and set the bool flag
    _.each(shouldDeactivate, function(itemName) {
        info[itemName].active = false;
        // our app did other work here to the component instance, etc.
    });

    _.each(items, function(item) {
        if(!info[item].active)
            info[item].active = true;
    });
};

Notice that when we call reduce, we pass in an empty array as the last argument – this is the initial state. As reduce iterates over the members of the info object, if the active property is true we push the key (the itemName) into the memo array (our state), and the memo is passed into the next iteration. Once reduce has “reduced” our list to only item names where active = true, the result is passed as the first argument to “difference” (an underscore function that takes two or more arrays and returns elements in the first array that don’t appear in the others). Our list of item names being activated is passed as the second argument to “difference”. This gives us an array of only active item names that are not in the list of item names we are activating. From there it’s simply a matter of iterating over the “shouldDeactivate” array, using the item name to look up the correct key on the info object, and setting active = false.

Hopefully this gives you an idea of how underscore’s reduce function can be useful to crawl an object, looking for members that match specific conditions. You could think of it, in this case, of being used like a selective “map” (in fact, you could easily replicate the same functionality by using “filter” and then “map” – but this does it in one operation).

The snippets above – while not doing anything “real”, are demonstrated here:

Tagged with:  
  • Daniel

    Thanks! Super helpful explanation of how to use reduce with anything other than numbers. Should be part of the Underscore documentation. Especially since they don’t list the order that objects are passed to the iterator function…

    • http://ifandelse.com Jim Cowart

      Thanks, Daniel!