0

I was in an interview and I was asked how to do this with JavaScript (kind of array manipulation) I said we can use reduce, but the interviewer said no we should use map but I am a bit sure that reduce can do anything that filter or map are doing. Am I correct or no?

Apologies, I can't reproduce the question I was asked, but I am asking here about the reducing technique generally in JavaScript...

customcommander
  • 17,580
  • 5
  • 58
  • 84
solimanware
  • 2,952
  • 4
  • 20
  • 40
  • 2
    in the same way that you can use your shoes to butter your toast, it's doable but the knife is more suitable for it – ibrahim mahrir Nov 18 '20 at 18:30
  • 1
    Yes, `reduce` *can* do everything `.map` can do. In fact, all the array iteration methods can be expressed in terms of `reduce`. That doesn't mean you *should* use `reduce` for all of them. Unless you are using [transducers](https://stackoverflow.com/questions/52008364/transducer-flatten-and-uniq) (also [see here](https://stackoverflow.com/questions/44198833/how-to-chain-map-and-filter-functions-in-the-correct-order)) you shouldn't - it's way more clear what you actually mean when you use the correct idiom for the job. Not to mention easier to implement – VLAZ Nov 18 '20 at 18:32
  • You always can do everything, the moment you have a callback, in which you can write arbitrary code, which has the same privileges. That doesn't mean it's a good idea. – ASDFGerte Nov 18 '20 at 18:34

2 Answers2

1

The answer is: ABSOLUTELY YES!

Reduce can in fact do anything that can be done by single iteration.

Filter:

myArray.reduce(
    (result, item) => [...result, ...(myCallback(item) ? [item] : [])],
    []
)

Map:

myArray.reduce(
    (result, item) => [...result, myCallback(item)],
    []
)

That being said, if you need to filter the items, use filter() and if you need to map the items, use map().

Robo Robok
  • 21,132
  • 17
  • 68
  • 126
  • 4
    That being said, if you wrote that code in a job interview, you'd more likely not get taken. Also, i don't know why this abomination of using spread in these small snippets became so popular, it turns everything from O(n) to O(n²) - not just here, it's been all over SO for a while now. – ASDFGerte Nov 18 '20 at 18:40
  • Do you have a proof it's O(n^2)? Also, how often do you use arrays with thousands of elements? For anything under, it doesn't matter. – Robo Robok Nov 18 '20 at 18:44
  • 1
    As long as the jitter isn't a lot smarter than i'd expect, and notices, that there aren't any references to the intermediate arrays, and therefore rewrites your code - you are copying the array on every step, and a standard sum from 1..n has an n² term, yes. You are correct, it very often doesn't matter. I just see it almost everywhere on SO these days, and i'd guess 1-5% of people using it will get in trouble, without even knowing why. – ASDFGerte Nov 18 '20 at 18:47
  • @VLAZ any proof it's faster? – Robo Robok Nov 18 '20 at 18:56
  • A difference from O(n) to O(n²) is so blatant, that you can just use both for an ever larger array, and eventually, one will freeze your browser, while the other will finish in milliseconds. – ASDFGerte Nov 18 '20 at 18:58
  • @RoboRobok I recently wrote this little gist showing that spreading quickly slow your code starting with only 1000 items. See https://gist.github.com/customcommander/97eb4b3f1600773db59406d39f3f9cd7 – customcommander Nov 18 '20 at 19:02
  • Just as a mention though, `Array.prototype.concat` "does not change the existing arrays, but instead returns a new array." - it's therefore the same as spreading. You have to e.g. `push`. – ASDFGerte Nov 18 '20 at 19:03
  • A typical demonstration would be the following: `const arr = [...Array.prototype.keys.call({ length: 20000 })]; arr.reduce((p, c) => (p.push(c), p), []); console.log("hey, i did finish!"); arr.reduce((p, c) => [...p, c], []);` (if you have a faster PC than i do, maybe you'll need 25000, but it will degenerate very quickly around that area - n² grows fast) – ASDFGerte Nov 18 '20 at 19:06
  • Also maybe for demonstration purposes, it was unwise to use spread to create the demonstration array - that's unimportant here, it just needs to be some array with 20000+ elements. You can use `Array.from({ length: 20000 }, (_, i) => i)` or even `Array(20000).fill(0)`. – ASDFGerte Nov 18 '20 at 19:17
  • Where did I use spread to create "demonstration array"? My demonstration array is `myArray`, which I never defined. – Robo Robok Nov 18 '20 at 19:19
  • I was referencing to my previous comment, where i use `[...Array.prototype.keys.call({ length: 20000 })]`, and can't edit anymore. It's slightly unwise to include a spread operator in a demonstration about spread operators, that isn't necessary. It may distract from the important part. Even though that is an actual iterator, and not an array i am spreading there. – ASDFGerte Nov 18 '20 at 19:20
  • @VLAZ I made a quick test and `concat()` performed ~2.5 times **slower** than spreading. 12s vs. 5s. – Robo Robok Nov 18 '20 at 19:24
1

Yes it can but that doesn't mean that you should swap Array#map for Array#reduce for any single task.

If you meant to transform all items in an array, then Array#map is just more practical:

[1,2,3].map(x => x + 10);
//=> [11,12,13]

// vs

[1,2,3].reduce((acc, x) => (acc.push(x + 10), acc), []);
//=> [11,12,13]

The canonical example for Array#reduce is to reduce a list to a single value. e.g., summing up the numbers in a list:

[1,2,3].reduce((tot, x) => tot + x, 0);
//=> 6

If you meant to transform and filter at the same time then Array#reduce may be helpful:

[1,true,2,false,3].filter(x => typeof x === 'number').map(x => x + 10);
//=> [11,12,13]

// vs

[1,true,2,false,3].reduce((acc, x) =>
  typeof x === 'number' ? (acc.push(x + 10), acc) : acc, []);
//=> [11,12,13]

Syntactically it could be argued that the .filter().map() approach looks prettier but it does iterate more than necessary: .filter() yields another array for .map() to consume.

If you have a big list to process, chaining array operations ala jQuery may be inefficient:

[items × 1000].filter(...).map(...).filter(...).map(...).filter(...);
// Depending on what you do at each step this could take a while
customcommander
  • 17,580
  • 5
  • 58
  • 84