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:
- Line one grabs the current item’s value for the given property.
- 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.
- Line three adds the item to that array we just added.
- 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' },
],
}
*/