Most efficient method to groupby on an array of objects
What is the most efficient way to groupby objects in an array?
For example, given this array of objects:
[
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
I’m displaying this information in a table. I’d like to groupby different methods, but I want to sum the values.
I’m using Underscore.js for its groupby function, which is helpful, but doesn’t do the whole trick, because I don’t want them “split up” but “merged”, more like the SQL group by
method.
What I’m looking for would be able to total specific values (if requested).
So if I did groupby Phase
, I’d want to receive:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
And if I did groupy Phase
/ Step
, I’d receive:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 15 },
{ Phase: "Phase 1", Step: "Step 2", Value: 35 },
{ Phase: "Phase 2", Step: "Step 1", Value: 55 },
{ Phase: "Phase 2", Step: "Step 2", Value: 75 }
]
Is there a helpful script for this, or should I stick to using Underscore.js, and then looping through the resulting object to do the totals myself?
If you want to avoid external libraries, you can concisely implement a vanilla version of groupBy()
like so:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Using ES6 Map object:
/**
* @description
* Takes an Array<V>, and a grouping function,
* and returns a Map of the array grouped by the grouping function.
*
* @param list An array of type V.
* @param keyGetter A Function that takes the the Array type V as an input, and returns a value of type K.
* K is generally intended to be a property key of V.
*
* @returns Map of the array grouped by the grouping function.
*/
//export function groupBy<K, V>(list: Array<V>, keyGetter: (input: V) => K): Map<K, Array<V>> {
// const map = new Map<K, Array<V>>();
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
return map;
}
// example usage
const pets = [
{type:"Dog", name:"Spot"},
{type:"Cat", name:"Tiger"},
{type:"Dog", name:"Rover"},
{type:"Cat", name:"Leo"}
];
const grouped = groupBy(pets, pet => pet.type);
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
const odd = Symbol();
const even = Symbol();
const numbers = [1,2,3,4,5,6,7];
const oddEven = groupBy(numbers, x => (x % 2 === 1 ? odd : even));
console.log(oddEven.get(odd)); // -> [1,3,5,7]
console.log(oddEven.get(even)); // -> [2,4,6]
About Map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
with ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);