Is there a more efficient way to filter this array of objects?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP


Is there a more efficient way to filter this array of objects?



I'm working with this array of objects and I try to filter and reorder it based on matching subjects. The array looks somewhat like this:


const results = [
{id: 1, subject: 'biology', grade: 10},
{id: 2, subject: 'biology', grade: 3},
{id: 3, subject: 'math', grade: 4},
{id: 4, subject: 'biology', grade: 2},
{id: 5, subject: 'geography', grade: 1},
{id: 6, subject: 'physics', grade: 3}
]



It's likely there will be added more subjects in the future.
With an over-abundant filtering method, I can arrange them the way I want:


var filteredObj = {};

filteredObj['biology'] = this.results.filter(function (result) {
return result.subject === 'biology';
});
filteredObj['math'] = this.results.filter(function (result) {
return result.subject === 'math';
});
filteredObj['physics'] = this.results.filter(function (result) {
return result.subject === 'physics';
});
filteredObj['geography'] = this.results.filter(function (result) {
return result.subject === 'geography';
});



Is there a way more efficient way of doing this?





Array is not a key-value store... filteredArray['biology'] looks incorrect to me.
– Kosh Very
yesterday


filteredArray['biology']





Also, just an FYI, var filteredArray = ; is not an array, it's actually an object if you're defining properties like filteredArray['biology'].
– FrankerZ
yesterday


var filteredArray = ;


filteredArray['biology']





@isherwood I'll take note. Dire need got the upper hand this time.
– Wes
yesterday




3 Answers
3



You can use Array.reduce() to group the items by subject:


Array.reduce()




const results = [{"id":1,"subject":"biology","grade":10},{"id":2,"subject":"biology","grade":3},{"id":3,"subject":"math","grade":4},{"id":4,"subject":"biology","grade":2},{"id":5,"subject":"geography","grade":1},{"id":6,"subject":"physics","grade":3}];

const grouped = results.reduce((r, o) => {
r[o.subject] = r[o.subject] || ;

r[o.subject].push(o);

return r;
}, Object.create(null));

console.log(grouped);



And it's easy to create a more generic groupBy function:


groupBy




const groupBy = (prop, arr) => arr.reduce((r, o) => {
r[o[prop]] = r[o[prop]] || ;

r[o[prop]].push(o);

return r;
}, Object.create(null));

const results = [{"id":1,"subject":"biology","grade":10},{"id":2,"subject":"biology","grade":3},{"id":3,"subject":"math","grade":4},{"id":4,"subject":"biology","grade":2},{"id":5,"subject":"geography","grade":1},{"id":6,"subject":"physics","grade":3}];

const grouped = groupBy('subject', results);

console.log(grouped);





Why do you use Object.create(null) instead of {}?
– FrankerZ
yesterday


Object.create(null)


{}





@FrankerZ It creates an empty object without prototype, and it prevents this statement - r[o.subject] || from not initializing an array because the subject's name is toString, for example. In this case it's probably not necessary, but it's a good precaution.
– Ori Drori
yesterday




r[o.subject] ||


toString





Returning a prototype-less object also means it has none of the methods one would expect on an objects. For example grouped.hasOwnProperty('biology') is a (potentially surprising) TypeError.
– Mark Meyer
yesterday


grouped.hasOwnProperty('biology')





@MarkMeyer - indeed. So it depends on what is the content you are trying to group, and what you'll use the object for.
– Ori Drori
yesterday





@MarkMeyer - a better option is to actually use a Map, and avoid these problems altogether.
– Ori Drori
yesterday





Try reduce:




const results = [
{id: 1, subject: 'biology', grade: 10},
{id: 2, subject: 'biology', grade: 3},
{id: 3, subject: 'math', grade: 4},
{id: 4, subject: 'biology', grade: 2},
{id: 5, subject: 'geography', grade: 1},
{id: 6, subject: 'physics', grade: 3}
];

const filteredArray = results.reduce((obj, item) => {
if (typeof obj[item.subject] === 'undefined') {
obj[item.subject] = [item];
} else {
obj[item.subject].push(item);
}

return obj;
}, {});

console.log(filteredArray);



With fancy es6 maps:




const results = [
{id: 1, subject: 'biology', grade: 10},
{id: 2, subject: 'biology', grade: 3},
{id: 3, subject: 'math', grade: 4},
{id: 4, subject: 'biology', grade: 2},
{id: 5, subject: 'geography', grade: 1},
{id: 6, subject: 'physics', grade: 3}
];

const filteredArray = results.reduce((obj, item) => {
const val = obj.get(item.subject) || ;

val.push(item);
obj.set(item.subject, val);

return obj;
}, new Map());

for (const [subj, val] of filteredArray) {
console.log(subj, val.length);
}
// console.log(JSON.stringify([...filteredArray]));



Using .map() to group an array of objects based of one of its properties:


.map()




const results = [{ id: 1, subject: 'biology', grade: 10 },
{ id: 2, subject: 'biology', grade: 3 },
{ id: 3, subject: 'math', grade: 4 },
{ id: 4, subject: 'biology', grade: 2 },
{ id: 5, subject: 'geography', grade: 1 },
{ id: 6, subject: 'physics', grade: 3 }
];

var filtered = {};
results.map((o, i) => {
if (!(o.subject in filtered)) filtered[o.subject] = ;
filtered[o.subject].push(results[i]);
});

console.log(filtered)



In this case, I prefer this way instead of .reduce() because the concept is more neat, if a property doesn't exist we create it in a new object, if is already created in the new object, we add the element.


.reduce()






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Keycloak server returning user_not_found error when user is already imported with LDAP

PHP parse/syntax errors; and how to solve them?

How to scale/resize CVPixelBufferRef in objective C, iOS