groupBy() in Javascript

groupBy() in Javascript

joel.
javascriptfunctional programming

Aug 31, 2016

problem

Sometimes it'd be nice to group an array of objects by a common key value, maybe an array of events. That way you could easily render them grouped by their starting time. Turns out there isn’t a groupBy() method out of the box in Javascript, but it isn’t too hard to add one for ourselves.

higher order functions to the rescue

There are several really powerful methods provided in Javascript like map(), filter(), sort(), and reduce(), these are called higher order functions. Using these as building blocks, we can build or own groupBy(), which we'll build using reduce().

The way reduce works is you give it an accumulator (some object where you want to store your results) and a function that determines how to combine a single item from the array into the accumulator. So essentially its purpose is to “reduce” an array into a single object by way of the function it is given. If you want to learn more about how reduce works, checkout the documentation.

the code

Array.prototype.groupBy = function(prop) {
  return this.reduce(function(groups, item) {
    const val = item[prop]
    groups[val] = groups[val] || []
    groups[val].push(item)
    return groups
  }, {})
}

explanation

Lets go through the code above to ensure we know what is really going on here.

Array.prototype.groupBy = function(prop) {
  ...
}

In Javascript we can declare our very own methods on objects. This means that we will be able to call our new groupBy() on any array object like <Array>.groupBy(prop). The prop will be the name of the property we want to group by.

return this.reduce(function(groups, item) {
  ...
}, {})

With our new method declared, we can access the array we are working on by using this. So we call reduce on our array and pass in two parameters, the first is our function we want to run on every item in the array, the second is the accumulator, or our starting point essentially. For this we'll use an empty object.

The reduce method will go through every item in the array and call our function on it, passing in the accumulator and the current item. The function’s job is to figure out where to put the item in the accumulator, and then return the updated accumulator.

var val = item[prop]
groups[val] = groups[val] || []
groups[val].push(item)
return groups

So now for the body of our function. We want to group all of our items based on their value for the given property. So if the property is name, and 3 items have a name of John, then we want a new list of all of the John’s. So to do that:

  1. Line one grabs the current item’s value for the given property.
  2. Line two is a check to see if we already have a new array setup for the value, if there isn’t one in the accumulator then an empty array is added.
  3. Line three adds the item to that array we just added.
  4. Line four returns the updated accumulator so it can be used with the next item.

All in all it is pretty simple, but it gets the job done. So lets see how it works with an example.

example

Let’s take a simple list of events with a time and a location.

const events = [
  { time: '12:00', location: 'mall' },
  { time: '9:00', location: 'store' },
  { time: '9:00', location: 'mall' },
  { time: '12:00', location: 'store' },
  { time: '12:00', location: 'market' },
]

We can use our new groupBy method to group them by their time value, like so.

const groupedByTime = events.groupBy('time')

/**
  groupedByTime = {
    '9:00': [
      { time: '9:00', location: 'store' },
      { time: '9:00', location: 'mall' },
    ],
    '12:00': [
      { time: '12:00', location: 'mall' },
      { time: '12:00', location: 'store' },
      { time: '12:00', location: 'market' },
    ],
  }
*/

We could also group them based on their location as well.

const groupedByLocation = events.groupBy('location')

/**
  groupedByLocation = {
    mall: [
      { time: '9:00', location: 'mall' },
      { time: '12:00', location: 'mall' },
    ],
    store: [
      { time: '9:00', location: 'store' },
      { time: '12:00', location: 'store' },
    ],
    market: [
      { time: '12:00', location: 'market' },
    ],
  }
*/

before you go, spread the word

joel.

Father. Husband. Web Developer at Deseret Book. Runner. Real Salt Lake fan.

join the newsletter

or don't, what do we care? but if you do, we promise to send cool stuff