95

flatMap is incredibly useful on collections, but javascript does not provide one while having Array.prototype.map. Why?

Is there any way to emulate flatMap in javascript in both easy and efficient way w/o defining flatMap manually?

Sergey Alaev
  • 3,851
  • 2
  • 20
  • 35
  • 11
    *"Why?"* Because no one made a proposal yet? [Here is how to propose a new feature](https://github.com/tc39/ecma262/blob/master/CONTRIBUTING.md#proposals). *"Is there any way to emulate flatMap ... w/o defining flatMap manually?"* Uh? I don't understand. You mean like `arr.reduce((arr, v) => (arr.push(...v), arr), [])`? – Felix Kling Oct 03 '16 at 18:04
  • (^that's only the flatten part, so I guess it should be `arr.map(...).reduce(...)`). – Felix Kling Oct 03 '16 at 18:10
  • You could just [flatten](http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript) the array after `.map`ping it. – kennytm Oct 03 '16 at 18:10
  • 1
    Hmm, you want to "emulate" it but not "define" it. What might that mean? –  Oct 03 '16 at 19:00
  • emulate = being able to use `flatMap`-like functionality via existing js functions. e.g. something like `[1, 2, 3].flatMap(x => [x, x]).flatten()`. Except a) there is no `flatten` b) it is ineffective and c) it could be shorter – Sergey Alaev Oct 04 '16 at 07:15
  • @FelixKling There was a proposal in Brian Terlson's github to add it to JS but I can't find it now. – Jared Smith Jul 13 '17 at 17:09
  • 8
    It is going to be part of JS soon. https://tc39.github.io/proposal-flatMap – Vijaiendran Nov 01 '17 at 20:57

8 Answers8

119

Update: Array.prototype.flatMap made it into ES2019

It is widely supported in many environments. See if it works in your browser using this snippet below -

const data =
  [ 1, 2, 3, 4 ]
  
console.log(data.flatMap(x => Array(x).fill(x)))
// [ 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ]

"Why no Array.prototype.flatMap in javascript?"

Because programming isn't magic and every language doesn't have features/primitives that every other language has. What matters is JavaScript gives you the ability to define it on your own -

const concat = (x,y) =>
  x.concat(y)

const flatMap = (f,xs) =>
  xs.map(f).reduce(concat, [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

Or a rewrite that collapses the two loops into one -

const flatMap = (f, xs) =>
  xs.reduce((r, x) => r.concat(f(x)), [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

If you want it to extend the Array.prototype, nothing is stopping you -

if (!Array.prototype.flatMap) {
  function flatMap (f, ctx) {
    return this.reduce
      ( (r, x, i, a) =>
          r.concat(f.call(ctx, x, i, a))
      , []
      )
  }
  Array.prototype.flatMap = flatMap
}

const ranks =
  [ 'J', 'Q', 'K', 'A' ]
  
const suits =
  [ '♡', '♢', '♤', '♧' ]

const result =
  ranks.flatMap(r =>
    suits.flatMap(s =>
      [[r, s]]
    )
  )

console.log(JSON.stringify(result))
// [ ['J','♡'], ['J','♢'], ['J','♤'], ['J','♧']
// , ['Q','♡'], ['Q','♢'], ['Q','♤'], ['Q','♧']
// , ['K','♡'], ['K','♢'], ['K','♤'], ['K','♧']
// , ['A','♡'], ['A','♢'], ['A','♤'], ['A','♧']
// ]
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    It is considered better to namespace any additions to prototypes (e.g. `Array.prototype._mylib_flatMap`), or even better use ES6 symbols, instead of simply defining `Array.prototype.flatMap`. – Fengyang Wang Oct 04 '16 at 02:32
  • 13
    @FengyangWang *"considered better"* is highly subjective. If this is your own app and you have a reason for extending your prototypes, there is nothing wrong with it. If it is an library/module/framework that you are distributing for others' use, then I would agree with you. Comments however are not the place to discuss this - the matter deserves more explanation and detail than should be provided in comments. – Mulan Oct 04 '16 at 07:05
  • First is ugly: flatMap defined as function will make code a mess - imagine functional code with `map` defined as global function! Second is just bad practice – Sergey Alaev Oct 04 '16 at 07:14
  • 3
    @SergeyAlaev re: *"imagine map as a global function"* haha I do exactly that on lots of my utility functions. I curry them too using sequences of arrow functions. "Ugly" is subjective. And you've made no argument to support your "bad practice" comment. Your remarks indicate to me you have little experience with functional programming. Take a language like Haskell where prelude include heaps of "globals"... no one is saying it's "ugly" or a "bad practice". You're just not familiar with it. – Mulan Oct 04 '16 at 07:34
  • 5
    @SergeyAlaev or consider Racket that has `append`, `map`, `filter`, `foldl`, `foldr` among countless others in the default namespace. It doesn't make it a bad practice because you heard someone say "globals are bad" or "extending natives is bad" - racket is amongst the best designed languages. You just don't know how/when to do it appropriately. – Mulan Oct 04 '16 at 07:47
  • 1
    @user633183 ""_considered better_" is highly subjective." No, it's not. It's conditional. Subjective isn't a synonym for conditional. Anyway, you outline those conditions in your first reply: "If it is your own app [...] there is nothing wrong with it." "If it is an library/module/framework that you are distributing for others' use, **then I would agree with you.**" I feel like this info should be in the body of the answer, as it's kinda important? – Elliott Jones Aug 02 '19 at 08:30
  • I think they just want an IIFE `;(()=>{/* that code */})();` – roberto tomás Aug 06 '19 at 13:19
  • Why is your order the parameters in a python-like map function call? I have always seen it written as iterable, followed by function in JavaScript. The thing that is being operated on is usually the most important thing and importance is usually left-to-right in JavaScript. – Mr. Polywhirl Jun 30 '20 at 17:43
  • It's not really important (and unrelated to Python) as you can define the function parameters for your program in any order of your choosing. You have seen `iter.map(func)` in JavaScript which is infix notation, popular with object-oriented style. In functional style, prefix notation like `map(func, iter)` or `map(iter, func)` are more common. Many popular functional languages put the "most important thing" last to enable easier function composition and partial function application. – Mulan Jul 02 '20 at 04:58
  • 1
    @elliott-jones Loved the distinction subjective/conditional. – JoeCool Feb 25 '21 at 14:08
52

flatMap has been approved by the TC39 as part of ES2019 (ES10). You can use it like this:

[1, 3].flatMap(x => [x, x + 1]) // > [1, 2, 3, 4]

Here's my own implementation of the method:

const flatMap = (f, arr) => arr.reduce((x, y) => [...x, ...f(y)], [])

MDN Article on flatMap

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
Kutyel
  • 8,575
  • 3
  • 30
  • 61
  • In particular, flapMap is officially in Node 11.0.0 according to MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap#Browser_compatibility – Carl Walsh Feb 20 '19 at 06:39
  • 8
    @CarlWalsh I'm sorry, I cannot resist. What the heck is `flapMap`? i want to flatten my map, not flap it! – ErikE Mar 19 '19 at 01:48
  • 1
    On another subject, this finally turns JavaScript Array's into Monads™️ – Kutyel Mar 29 '19 at 17:54
11

I know you said you didn't want to define it yourself, but this implementation is a pretty trivial definition.

There's also this from the same github page:

Here is a bit of shorter way using es6 spread, similiar to renaudtertrais's - but using es6 and not adding to the prototype.

var flatMap = (a, cb) => [].concat(...a.map(cb))

const s = (v) => v.split(',')
const arr = ['cat,dog', 'fish,bird']

flatMap(arr, s)

Would either of these help?

It should be noted (thanks to @ftor) that this latter "solution" suffers from "Maximum call stack size exceeded" if called on a really large (e.g., 300k elements) array a.

Alan Mimms
  • 651
  • 9
  • 22
  • 2
    This implementation may blow up the stack for large arrays. –  Oct 04 '16 at 07:08
  • Just to be clear, if I understand you correctly, @ftor, you're talking about the recursive implementation in the link I gave, not the one I show in the code block at the end of my answer, right? – Alan Mimms Oct 04 '16 at 22:28
  • 2
    Try `[].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase())))`. Sure, it's a corner case. But you should at least mention it. –  Oct 05 '16 at 07:11
  • Thanks. I added a note to this effect. – Alan Mimms Oct 06 '16 at 15:43
7

Lodash provides a flatmap function, which to me is practically equivalent to Javascript providing it natively. If you're not a Lodash user, then ES6's Array.reduce() method can give you the same result, but you have to map-then-flatten in discrete steps.

Below is an example of each method, mapping a list of integers and returning only the odds.

Lodash:

_.flatMap([1,2,3,4,5], i => i%2 !== 0 ? [i] : [])

ES6 Reduce:

[1,2,3,4,5].map(i => i%2 !== 0 ? [i] : []).reduce( (a,b) => a.concat(b), [] )
Matthew Marichiba
  • 1,942
  • 1
  • 23
  • 24
4

One fairly concise approach is to make use of the Array#concat.apply:

const flatMap = (arr, f) => [].concat.apply([], arr.map(f))

console.log(flatMap([1, 2, 3], el => [el, el * el]));
JLRishe
  • 99,490
  • 19
  • 131
  • 169
1

I did somthing like this:

Array.prototype.flatMap = function(selector){ 
  return this.reduce((prev, next) => 
    (/*first*/ selector(prev) || /*all after first*/ prev).concat(selector(next))) 
}

[[1,2,3],[4,5,6],[7,8,9]].flatMap(i => i); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

[{subarr:[1,2,3]},{subarr:[4,5,6]},{subarr:[7,8,9]}].flatMap(i => i.subarr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

serhii
  • 41
  • 4
  • Hi serhii, I have tried this code, when parent only one object will gives unexpected result. ``` Array.prototype.flatMap = function(selector){ if (this.length == 1) { return selector(this[0]); } return this.reduce((prev, next) => (/*first*/ selector(prev) || /*all after first*/ prev).concat(selector(next))) }; ``` – Johnny Feb 15 '18 at 13:25
1

We now have a flatMap() in Javascript! And it is supported pretty well

The flatMap() method first maps each element using a mapping function, then flattens the result into a new array. It is identical to a map() followed by a flat() of depth 1

const dublicate = x => [x, x];

console.log([1, 2, 3].flatMap(dublicate))
bigInt
  • 203
  • 5
  • 15
  • Getting error when running the posted code `{ "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 }` – SolutionMill Jun 13 '19 at 18:23
  • What broser are you using? Edge, IE and Samsung Internet does not support it yet, thoug, Edge will soon run on V8. So to support those, you could use a polyfil – bigInt Jun 13 '19 at 18:25
  • Chrome Version 67.0.3396.87 (Official Build) (64-bit) – SolutionMill Jun 13 '19 at 18:26
  • Your browser is outdated.. You should update it or set to update itself automaticly. – bigInt Jun 13 '19 at 18:28
0

Array.prototype.flatMap() has now arrived in JS. However, not all browser might support them check for the current browser compatibility the Mozilla web docs.

What the flatMap() method does is first maps each element using a callback function as argument, then flattens the result into a new array (the 2d array is now 1d since the elements are flattened out).

Here is also an example of how to use the function:

let arr = [[2], [4], [6], [8]]

let newArr = arr.flatMap(x => [x * 2]);

console.log(newArr);
Willem van der Veen
  • 33,665
  • 16
  • 190
  • 155