3
\$\begingroup\$

I came across a problem in my NodeJS app, which I solved by creating a method called groupBy. The purpose of this method is simple: take an array, and a function which will select a field from an element in the array. Each element in the array should then be grouped based on this field and returned as an object with the key being the value of the field selected.

var groupBy = function(arr, keySelector) {
    var map = {};
    arr.map(function(element) {
        var key = keySelector(element);
        if(key === undefined)
            throw 'undefined key!';

        map[key] = map[key] || [];
        map[key].push(element);
    });
    return map;
};

It works perfectly, but could it be improved in any way?

\$\endgroup\$
5
\$\begingroup\$

Looks OK.

Though I have to ask why you're using map, when you're not really mapping the array to a new array. Using reduce would be more semantically correct, but in this case even just replacing map with forEach would also be more accurate. (The difference is literally only skin-deep; there's zero change in overall functionality.)

I might ask why an undefined value should throw an exception, though. It'll work fine without it, and I might want to group objects based on whether or not they have a certain property or not. Without the exception, I'd be able to get back an object with some of the elements grouped by the key "undefined"

And if I were super nit-picky, I'd say you should always using braces even for one-line "block" like your throw line.

I'm betting, though, that a lot of the time, you'll want to group by a simple, named key, in which case it'd be much easier to pass in a string, rather than a function. A custom function is still very useful, though, so it'd be nice to support both.

I might do:

function groupBy(array, keyOrIterator) {
  var iterator, key;

  // use the function passed in, or create one
  if(typeof key !== 'function') {
    key = String(keyOrIterator);
    iterator = function (item) { return item[key]; };
  } else {
    iterator = keyOrIterator;
  }

  return array.reduce(function (memo, item) {
    var key = iterator(item);
    memo[key] = memo[key] || [];
    memo[key].push(element);
    return memo;
  }, {});
}

Lastly: Libraries like underscore.js and it's "doppelgänger" lo-dash has this and many other helpful functions, ready for you to use. I'm only mentioning it because if you found yourself needed a groupBy function, you'll probably find yourself needing other similar functions, and libraries like those provide that in spades.

\$\endgroup\$
  • \$\begingroup\$ I might ask why an undefined value should throw an exception, though - I don't think an undefined key is suitable in my usecase, although it is good food for thought. The reason why I used a function is simply because I'm spoiled by C#'s GroupBy :). I actually came across lodash after writing this function, but I was interested in feedback all the same. \$\endgroup\$ – Dan Pantry Oct 15 '14 at 12:14
  • \$\begingroup\$ I'm glad you referenced underscore and lo-dash -- those are highly used and heavily tested lightweight librarires. If you really wanted a standalone groupby function, why not look at what is already out there (open source)? \$\endgroup\$ – y3sh Oct 15 '14 at 12:56
  • \$\begingroup\$ Hindsight is 20/20 - I didn't want to go searching for a library to do this that and the other. I knew what I wanted to achieve and wrote a function for it. I'd heard of lodash but never actually used it before, only saw it in passing on npm dependencies. I wouldn't've known to look for something I didn't know existed after all ;-) \$\endgroup\$ – Dan Pantry Oct 15 '14 at 13:26
  • \$\begingroup\$ @DanPantry Hindsight is always 20/20 with such things. I know I've written a lot of code I've later found libraries for. But at least it's always a learning experience in one way or another. Either you learn something just by doing your own implementation, or you learn something from looking at the library's implementation, or you just learn what libraries to look for. Or - once in a blue moon - you learn that your implementation is better, in one way or another, than any of the libraries' :) \$\endgroup\$ – Flambino Oct 15 '14 at 13:33
  • \$\begingroup\$ The code of @Flambino is not working, here's the working version : function groupBy(array, keyOrIterator) { var iterator, key; // use the function passed in, or create one if(typeof keyOrIterator !== 'function') { key = String(keyOrIterator); iterator = function (item) { return item[key]; }; } else { iterator = keyOrIterator; } return array.reduce(function (memo, item) { var key = iterator(item); memo[key] = memo[key] || []; memo[key].push(item); return memo; }, {}); } \$\endgroup\$ – Benoit Sep 3 at 7:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.