break array of objects into separate arrays based on a property

Say I have an array like this:

var arr = [
{type:"orange", title:"First"},
{type:"orange", title:"Second"},
{type:"banana", title:"Third"},
{type:"banana", title:"Fourth"}
];

and I want this to be split up into arrays that have objects which have same type so:

[{type:"orange", title:"First"},
{type:"orange", title:"Second"}]


[{type:"banana", title:"Third"},
{type:"banana", title:"Fourth"}]

But I want to do this generically so not having an if statement that specifies orange or banana

// not like this
for (prop in arr){
if (arr[prop] === "banana"){
//add to new array
}
}

Thoughts? JQuery and Underscore are both options to use.

75058 次浏览

This assumes an array of objects:

function groupBy(array, property) {
var hash = {};
for (var i = 0; i < array.length; i++) {
if (!hash[array[i][property]]) hash[array[i][property]] = [];
hash[array[i][property]].push(array[i]);
}
return hash;
}


groupBy(arr,'type')  // Object {orange: Array[2], banana: Array[2]}
groupBy(arr,'title') // Object {First: Array[1], Second: Array[1], Third: Array[1], Fourth: Array[1]}

Just build a dictionary which holds the objects based on their title. You could do it like this:

js

var arr = [
{type:"orange", title:"First"},
{type:"orange", title:"Second"},
{type:"banana", title:"Third"},
{type:"banana", title:"Fourth"}
];
var sorted = {};
for( var i = 0, max = arr.length; i < max ; i++ ){
if( sorted[arr[i].type] == undefined ){
sorted[arr[i].type] = [];
}
sorted[arr[i].type].push(arr[i]);
}
console.log(sorted["orange"]);
console.log(sorted["banana"]);

jsfiddle demo: http://jsfiddle.net/YJnM6/

This is an easy job for Array.reduce(...):

function groupBy(arr, property) {
return arr.reduce(function(memo, x) {
if (!memo[x[property]]) { memo[x[property]] = []; }
memo[x[property]].push(x);
return memo;
}, {});
}


var o = groupBy(arr, 'type'); // => {orange:[...], banana:[...]}
o.orange; // => [{"type":"orange","title":"First"},{"type":"orange","title":"Second"}]
o.banana; // => [{"type":"banana","title":"Third"},{"type":"banana","title":"Fourth"}]

Of course, if your target browser(s) do not support ECMAScript 262 5th edition then you'll have to implement "reduce" by yourself, or use a polyfill library, or choose another answer.

[Update] Here's a solution that should work with any version of JavaScript:

function groupBy2(xs, prop) {
var grouped = {};
for (var i=0; i<xs.length; i++) {
var p = xs[i][prop];
if (!grouped[p]) { grouped[p] = []; }
grouped[p].push(xs[i]);
}
return grouped;
}

JQuery and Underscore are both options to use.

Underscore's groupBy does exactly what you need.

_.groupBy(arr, "type")

Typescript version.

/**
* Group object array by property
* Example, groupBy(array, ( x: Props ) => x.id );
* @param array
* @param property
*/
export const groupBy = <T>(array: Array<T>, property: (x: T) => string): { [key: string]: Array<T> } =>
array.reduce((memo: { [key: string]: Array<T> }, x: T) => {
if (!memo[property(x)]) {
memo[property(x)] = [];
}
memo[property(x)].push(x);
return memo;
}, {});


export default groupBy;

ES6 solution:

function groupBy(arr, property) {
return arr.reduce((acc, cur) => {
acc[cur[property]] = [...acc[cur[property]] || [], cur];
return acc;
}, {});
}

or completely ES6fy:

const groupBy = (arr, property) => {
return arr.reduce((acc, cur) => {
acc[cur[property]] = [...acc[cur[property]] || [], cur];
return acc;
}, {});
}

I hope it helps!

You can also use https://lodash.com/docs/4.17.15#groupBy

It will serve your purpose

Little tweak to @Watchmaker's answer to return array instead of object.:

function groupBy(arr, key) {
return arr.reduce((acc, cur) => {
acc[cur[key]] = [...acc[cur[key]] || [], cur];
return acc;
}, []).filter(Boolean);
}

for an instance if we dont want to mention type then we can achieve like this

var arr = [
{type:"orange", title:"First"},
{type:"orange", title:"Second"},
{type:"banana", title:"Third"},
{type:"banana", title:"Fourth"}
];


const fun = (ar)=>{
let temp ={}
for(e of ar){
!temp[e.type] ? temp[e.type]=[e] : temp[e.type].push(e)
}
return temp
}
console.log(fun(arr))

Another typescript version

Originally inspired by the answer of @denolsson but I remove the property that is used as key as it is now redundant in the inner arrays.

Also changed and renamed some other things for better readability and to make the linter happy)

/**
* Inspired by https://stackoverflow.com/a/53632546/7869582
*/
private groupBy<T>(array: Array<T>, property: keyof T): { [key: string]: Array<T> } {
return array.reduce(
(objectToBeBuilt: { [key: string]: Array<T> }, arrayElem: T) => {
            

const newOuterIdx = arrayElem[property] as unknown as string;
            

if (!objectToBeBuilt[newOuterIdx]) {
objectToBeBuilt[newOuterIdx] = [];
}
            

if (arrayElem[property]) {
delete arrayElem[property];
}


objectToBeBuilt[newOuterIdx]?.push(arrayElem);
return objectToBeBuilt;
},
{}  // initial value of objectToBeBuild
);
}

Example usage:

const resultGroupedByCustomer = this.groupBy(result.rows, "customer_id");