10 ms·
Flatten Arrays in Vanilla JavaScript with Flat() and FlatMap()
- Waterluvian 5y agoI've always found `flatMap` to be interesting because it feels like a convenience function, combining .map(fn).flat() into one function. It's interesting because JS doesn't really have a lot of convenience functions that are this shallow (ie. that provide just minimal cleanup compared to the functions they're wrapping). Is there some specific reason that `flatMap` made the cut?
- jmath 5y agoperhaps it’s a case of “names are fun”
- beaconstudios 5y agoprobably because flatMap (also called chain) is an important array (or monads in general) function in functional programming. If anything, .flat() is the aberration.
- ReleaseCandidat 5y ago> If anything, .flat() is the aberration. Same as `flatmap` (`bind`), `flat` is 'part' of a monad (as `join`).
- beaconstudios 5y agogood point, I didn't think of that equivalence.
- contravariant 5y agoIn mathematics it would be 'flat' that is the important one, not flatMap. You get 'flatMap' by just composing the monad multiplication 'flat' with the monad functor's mapping of arrows 'map'. That's mainly just to highlight the similarity with regular monoids though. But in a program it usually makes sense to do the flattening in one go, since it avoids the need to do some possibly expensive intermediate calculations.
- deleted 5y ago[deleted]
- HellzStormer 5y agoSmall mistake, and maybe that's why this function exists, but it's actually combines `.map(fn).flat(1)`. I think conceptually, `flatMap` is a `map` where each iteration can return multiple values (or none). So it feels quite more powerful than just `map` and is a quite common convenience function.
- mstade 5y agoI think the depth argument of flat defaults to 1, so it'd be the same anyway. Not sure why the default is 1, I'd prefer Infinity so it'd always flatten everything by default, but maybe there are some good reasons for it.
- Waterluvian 5y agoI think it should be explicit without a default. But in the real world, if we must have a default, I think it should be 1, not infinity. Infinity has a frustrating fail state, in my opinion: you might be flattening arrays that weren't meant to be flattened. And now you've got a cluster!@#$ of data to look at and reason about. The fail state of 1 is that it doesn't flatten enough but you have the original structure in your 1-level flattened output. So you're far more likely to make heads and tails of how much more flattening you need.
- mstade 5y agoArguably the same argument works the other way around as well, that in thinking flat recursively flattens all arrays of arrays you get confused when it doesn't. (I've definitely run across that footgun.) For me, I just find that in almost all cases I've used flat it's been with Infinity as the depth value. It seems to me as well that most of the time you'd probably either want 1 or Infinity, not 5 or 12 or whatever, so perhaps it would've even been better to just have two functions: flat and flatDeep (or some such, naming is hard) the latter of which would default to Infinity but allow different depths as well. Probably reads better than just flat as well, e.g. `.flatDeep(5)` rather than `.flat(5)`. Oh well, we're stuck with this now so it's all an academic exercise anyway, but I'd be curious to know the rationale of the design. Maybe I'll wade through the spec repo one day to see if there's any discussion about it.
- Tuna-Fish 5y agoFor a lot of common cases (where the returned lists from the fn are short, but the complete list is long), a combined flatMap can be optimized to be much faster than doing the functions one by one.
- vmchale 5y agoIn J this is just , (ravel) https://code.jsoftware.com/wiki/Vocabulary/comma https://code.jsoftware.com/wiki/Vocabulary/comma
- rak1507 5y agoMore like ; than ,
- yawnxyz 5y agoI've made it a habit to check Mozilla's JS docs once in a while for functions like Flat() and FlatMap(). Sometimes if I find myself reaching for underscore/lodash, I'll check Mozilla docs first to see if there's some new function that can let me omit using lodash. I'm often delighted to find new convenience functions I can just use without adding another dependable.
- newsbinator 5y agoI often use Lodash’s sumBy, groupBy and orderBy. Love the versatility and one-liner aspect. E.g. sumBy(“orders”, “total.amount”)
- johndough 5y agoWhich input method are you using that double quotes are ” (U+201C) instead of " (U+0022)?
- newsbinator 5y agoWow, no clue. That was on mobile... web app or Octal (native app). Stock iOS English (US) keyboard.
- notreallyserio 5y agoNot that you have to, but just FYI you can disable "smart" punctuation in iOS Settings: General -> Keyboard -> Smart Punctuation. I don't know why they omitted the quotes around smart there.
- saranshk 5y agothere have been a lot of those added to the ES spec lately and a lot more coming soon. So it is always good to check MDN because I usually end up learning something new mostly.
- copperx 5y agoIs there a Google equivalent to MDN?
- Rygian 5y agoI have very little experience with arrays in JS, but I always have a nagging feeling that, if my algorithm needs me to flatten an array, then there is something wrong with the data structures I am using. Example in the article is meaningless to me: array.map(x => [x \* 2]); // [[2], [4], [6], [8]] array.flatMap(x => [x * 2]); // [2, 4, 6, 8] because the right way would be array.map(x => x *2); anyway. What am I missing? What is a realistic scenario where an array needs to be flattened?
- thaunatos 5y agoConsider a parent -> child relation. You have an array `parents`, but want all their children. parents.flatMap(parent => parent.children)
- beardedetim 5y agoLet's say I have a list of friend objects: myFriends: { name: string, friends: friend[] }[] If I wanted to get a list of friends of friends, I could do something like myFriends.map(prop('friends')).flatten() Or myFriends.flatMap(prop('friends')) My data is a list of lists but I really want a list of friends so I flatten. Maybe the Friend object is poorly created/designed but often times you'll just have to deal with whatever the API gives you.
- deleted 5y ago[deleted]
- rak1507 5y agoSome sort of function that returns a variable number of arguments. It's basically the same as the list monad in haskell, and there are examples here https://en.wikibooks.org/wiki/Haskell/Understanding_monads/List https://en.wikibooks.org/wiki/Haskell/Understanding_monads/L... that can be followed even if you don't know any Haskell.
- piaste 5y ago> What is a realistic scenario where an array needs to be flattened? Concatenating the result of a paginated API. Showing all the objects two or more 1:N steps away from you in the object graph. The events your friends are attending, the issues your coworkers are working on, the people belonging to any of your same groups. Basically any time you would do a SELECT... JOIN in SQL.
- deleted 5y ago[deleted]
- heydenberk 5y agoSomething that people may not see immediately is that flatMap is more general than map and filter. Say, for a contrived example, that you'd like to filter out the even numbers in an array, and then double the odd numbers that remain. Instead of: [1, 2, 3, 4, 5].filter(n => n % 2 === 1).map(n => n * 2) You can do: [1, 2, 3, 4, 5].flatMap(n => n % 2 === 1 ? [n * 2] : []) Again, this is a contrived example, but I think it's interesting since the generality is not obvious (to me)
- nesarkvechnep 5y agoOr… use reduce.
- ReleaseCandidat 5y agoYes, but you could also use `fold`^H^H^H^H`reduce`. [1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.push(2*n) : acc, [])
- christophilus 5y agoPush returns the length of the array, though, so that won't work.
- ReleaseCandidat 5y agoAh, sorry, so you have to concat the arrays using `concat`. [1, 2, 3, 4, 5].reduce((acc, n) => n % 2 === 1 ? acc.concat([2*n]) : acc, [])
- krylon 5y agoThis stirs up some memories of Perl, where flattening arrays/lists is the default, and you need explicit references to have nested arrays or hash tables. I remember it used to be a kind of foot-seeking gun for newbies, but I never ran into trouble with it once I understood references (which isn't super hard).
- _greim_ 5y agoI'm glad JS is adding these methods to Array. It would be nice if iterables had similar methods for working with lazy sequences, similar to Rust, but this isn't practical since "iterable" is a protocol. Array can do it because it's a class. Perhaps JS could also adopt something similar to Rust's traits, such that implementing the protocol would make a set of related methods callable on that object.
- scotty79 5y agoA lot of funtional primitives have really counterintuitive names for me. flatMap is one of them.
- saranshk 5y agoagreed, though I doubt there's anything we can do about it.