最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - FlatMap vs Reduce for mapping and filtering - is one recommended over the other? - Stack Overflow

programmeradmin2浏览0评论

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
 |  Show 4 more comments

3 Answers 3

Reset to default 10

Neither 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:

  1. Depending on your OS & browser, reduce is usually fastest but occasionally taken over by the simple loop, filter+map follows closely behind in performance and flatMap is always the slowest option.
  2. 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);

发布评论

评论列表(0)

  1. 暂无评论