According to this SO thread, flatMap
or reduce
are recommended when one needs to map and filter on an array of objects, especially if they want to avoid looping through the collection multiple times. Personally, I prefer using flatMap
as I think it is easier to read, but that is entirely subjective of course. Aside from browser compatibility issues for flatMap
, is one method generally recommended or beneficial over the other (perhaps because of, but not limited to, reasons of performance or readability)?
Update: Here is an example from the referenced answer of flatMap filtering:
var options = [{
name: 'One',
assigned: true
},
{
name: 'Two',
assigned: false
},
{
name: 'Three',
assigned: true
},
];
var assignees = options.flatMap((o) => (o.assigned ? [o.name] : []));
console.log(assignees);
document.getElementById("output").innerHTML = JSON.stringify(assignees);
<h1>Only assigned options</h1>
<pre id="output"> </pre>
According to this SO thread, flatMap
or reduce
are recommended when one needs to map and filter on an array of objects, especially if they want to avoid looping through the collection multiple times. Personally, I prefer using flatMap
as I think it is easier to read, but that is entirely subjective of course. Aside from browser compatibility issues for flatMap
, is one method generally recommended or beneficial over the other (perhaps because of, but not limited to, reasons of performance or readability)?
Update: Here is an example from the referenced answer of flatMap filtering:
var options = [{
name: 'One',
assigned: true
},
{
name: 'Two',
assigned: false
},
{
name: 'Three',
assigned: true
},
];
var assignees = options.flatMap((o) => (o.assigned ? [o.name] : []));
console.log(assignees);
document.getElementById("output").innerHTML = JSON.stringify(assignees);
<h1>Only assigned options</h1>
<pre id="output"> </pre>
Share
Improve this question
edited Aug 25, 2020 at 4:15
hgb123
14.9k3 gold badges23 silver badges43 bronze badges
asked Aug 21, 2020 at 16:17
LockettKsLockettKs
4821 gold badge4 silver badges12 bronze badges
9
- 6 If there is a problem with my question let me know and I will try to fix it. Downvoting without any context is not helping me answer my question. – LockettKs Commented Aug 21, 2020 at 18:08
- You cannot filter anything with flatMap. flatmap is the same as map followed by flat. Reduce is not recommended in most cases because it's hard to read. – Konrad Commented Aug 21, 2020 at 20:46
- 4 @Konowy Actually you can use flatMap to filter. I updated my question with an example of this in action. – LockettKs Commented Aug 24, 2020 at 16:25
- I don't see the example? – Konrad Commented Aug 24, 2020 at 16:30
- I got interrupted earlier while I was in the middle of writing it. It's added now @KonradLinkowski – LockettKs Commented Aug 25, 2020 at 2:39
3 Answers
Reset to default 10Neither actually. For mapping and filtering, your should use map
and filter
:
var assignees = options.filter(o => o.assigned).map(o => o.name);
Much simpler and more readable, it directly expresses your intent. Nothing beats that on clarity.
If you care about performance, always benchmark to see the actual impact on your real-world case - but don't prematurely optimise.
Depending on what exactly you want to do with each item, pushing to a reduce accumulator is generally going to be much faster, especially if your array ends up expanding. As many will point out though, unless you're working with big data, the increase in performance is usually not worth making your code less readable.
To choose between the two you mention, I would still use the reduce option, since to me it's actually the clearest about what's happening in your example. Reduce is all about digesting arrays and giving you back the nutrient results you need.
var options = [
{ name: 'One', assigned: true },
{ name: 'Two', assigned: false },
{ name: 'Three', assigned: true },
];
console.time();
var assignees = options.reduce((a, o) => (o.assigned && a.push(o.name), a), []);
console.timeEnd();
console.log(assignees);
Two things to note about the above code in case you're having difficulty parsing it:
- The && short-circuit operator here returns o.assigned if falsy or moves on and returns the evaluated push if truthy.
- The , (comma operator) tells the runtime to complete the previous expression and then return what comes after, which is the now mutated variable a. Wrapping it all in brackets is necessary so js knows the comma is part of the arrow function, not a separator.
To me, the above is clear and easy to read, but it can be difficult if you're not properly familiar with those two operators. You can make it clearer and remove the mutation done with push (some prefer to avoid mutations at all costs, even when neatly contained in a single call) by returning o.assigned ? a.concat([o.name]) : a
instead, but this will exponentially kill the performance and you can easily run out of memory slots as you scale up. The spread operator a la [...a, o.name]
is even worse. Never use either of these with reduce.
For flatMap, removing empty elements of sparse arrays is simply a side-effect of using flat
and by extension flatMap
, when its real purpose is to spread nested arrays into the parent. So using it without a multi-dimensional array, especially with the performance hit, does not make much sense to me even though it's quite common. Where it would make sense is something like the following:
var options = [
{ names: ['One', 'Two', 'Three'], assigned: true },
{ names: ['Four', 'Five', 'Six'], assigned: false },
{ names: ['Seven', 'Eight', 'Nine'], assigned: true },
];
console.time();
var assignees = options.flatMap((o) => (o.assigned) ? o.names : []);
console.timeEnd();
console.log(assignees);
If you used reduce for the above, you'd either need to explicitly use the spread operator like so: a.push(...o.names)
, or resort to a forEach like this: o.names.forEach(name => a.push(name))
. (Yes, there are worse options, too.) It should be obvious that both these options are kludgy compared to flatMap
in this case. You could of course just add the .flat()
to the end of .filter(...).map(...)
, but that's exactly what flatMap
is meant to replace, hence its elegance here.
Of course, if there's a possibility that you will be using the filtered list more than once, it may be better to filter
your options first and then run map
or whatever you need on the filtered list like @Bergi shows in his answer. If your needs are simple enough, though, another option is to just roll your own function making use of a for loop; then you can name it as semantically as you like. (Running the loop directly rather than setting up a function should be quicker, but you lose flexibility.) I've included all four options with basic benchmarks below:
var options = Array.from({length:1000000}, (v, i) => ({
name: String(i),
assigned: Math.random() < 0.5
}));
function getAssignees(list) {
var assignees = [];
for (var o of list) if (o.assigned) assignees.push(o.name);
return assignees;
}
console.time('a');
var assigneesA = options.reduce((a, o) => (o.assigned && a.push(o.name), a), []);
console.timeEnd('a');
console.time('b');
var assigneesB = options.flatMap((o) => (o.assigned) ? [o.name] : []);
console.timeEnd('b');
console.time('c');
var assigneesC = getAssignees(options);
console.timeEnd('c');
console.time('d');
var assigneesD = options.filter(o => o.assigned).map(o => o.name);
console.timeEnd('d');
If you run the above enough times, you should notice two things:
- Depending on your OS & browser,
reduce
is usually fastest but occasionally taken over by the simple loop,filter
+map
follows closely behind in performance andflatMap
is always the slowest option. - It takes a million records to really see any appreciable difference in speed, and even then we're looking at milliseconds, so it's seldom worth worrying about.
What you should be thinking about is how the rest of your code is structured, where and how often you need to run something like this, and what will make your intentions clearest. @Bergi makes a good case for filter
+map
here as it's certainly the cleanest, clearest and easiest to read, but it's two-pass rather than one and learning how to use reduce properly doesn't hurt. You may, after all, end up needing it down the road for more complex and performance-sensitive transformations of your data.
I would use a reduce in this case.
var options = [{
name: 'One',
assigned: true
},
{
name: 'Two',
assigned: false
},
{
name: 'Three',
assigned: true
},
];
var assignees = options.reduce((results, item) => {
if (item.assigned) {
results.push(item.name);
}
return results;
}, []);
console.log(assignees);