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

Multi tool use


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?
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.
Array is not a key-value store...
filteredArray['biology']
looks incorrect to me.– Kosh Very
yesterday