为什么javascript中没有Array.prototype.flatMap?

IT技术 javascript functional-programming
2021-03-13 20:19:34

flatMap在集合上非常有用,但是 javascript 在拥有Array.prototype.map. 为什么?

有没有办法以flatMap简单有效的方式在 javascript 中进行模拟而无需flatMap手动定义

6个回答

更新: Array.prototype.flatMap进入 ES2019

它在许多环境中得到广泛支持。使用以下代码段查看它是否在您的浏览器中有效 -

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 ]


“为什么 JavaScript 中没有 Array.prototype.flatMap?”

因为编程不是魔术,而且每种语言都没有其他语言所具有的特性/原语。重要的是 JavaScript 让你能够自己定义它——

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))

或者重写将两个循环合二为一——

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))

如果你想让它延长Array.prototype,没有什么能阻止你 -

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','♧']
// ]

@SergeyAlaev 或考虑在默认命名空间中具有append, map, filter, foldl,foldr以及无数其他名称的Racket 这并不是一个糟糕的做法,因为你听到有人说“全局变量不好”或“扩展本地语言不好”——球拍是设计最好的语言之一。你只是不知道如何/何时适当地去做。
2021-04-23 20:19:34
@FengyangWang “被认为更好”是非常主观的。如果这是您自己的应用程序并且您有理由扩展您的原型,那么它没有任何问题。如果它是您分发供他人使用的库/module/框架,那么我会同意您的看法。然而,评论不是讨论这个问题的地方——这个问题值得在评论中提供更多的解释和细节。
2021-04-24 20:19:34
最好为原型的任何添加命名命名空间(例如Array.prototype._mylib_flatMap),或者甚至更好地使用 ES6 符号,而不是简单地定义Array.prototype.flatMap.
2021-05-03 20:19:34
@user633183“”被认为更好“是非常主观的。” 不,这不对。这是有条件的。主观不是条件的同义词。无论如何,您在第一个回复中概述了这些条件:“如果这是您自己的应用程序 [...] 没有任何问题。” “如果它是您分发供他人使用的库/module/框架,那么我会同意您的看法。 ”我觉得这个信息应该在答案的正文中,因为它有点重要?
2021-05-14 20:19:34
@SergeyAlaev 回复:“把地图想象成一个全局函数”哈哈,我在很多实用函数上都是这样做的。我也使用箭头函数序列对它们进行了咖喱处理。“丑”是主观的。而且您没有提出任何论据来支持您的“不良做法”评论。您的评论向我表明您对函数式编程的经验很少。以 Haskell 这样的语言为例,它的前奏包括大量的“全局变量”……没有人说它是“丑陋的”或“不好的做法”。你只是不熟悉它。
2021-05-15 20:19:34

flatMap已被 TC39 批准为 ES2019 (ES10) 的一部分。你可以这样使用它:

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

这是我自己对该方法的实现:

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

关于 flatMap 的 MDN 文章

在另一个主题上,这最终将 JavaScript 数组变成了 Monads™️ 🙃
2021-04-23 20:19:34
@CarlWalsh 对不起,我无法抗拒。到底是flapMap什么?我想压平我的地图,而不是拍打它!
2021-05-07 20:19:34
特别是,根据 MDN,flapMap 正式在 Node 11.0.0 中:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-05-19 20:19:34
这是一个旧的答案,但效率很低,尤其是在大型数组上(创建一个新数组并在每次迭代时复制所有先前的元素)。另一种方法是使用 concat const flatMap = (arr, ...args) => [].concat(...arr.map(...args));(或者apply如果您不能使用扩展运算符,则使用) - 尽管这个 concat 版本对于大数组存在调用堆栈大小问题,如解决方案中所述
2021-05-20 20:19:34

我知道你说过你不想自己定义它,但这个实现是一个非常简单的定义。

还有来自同一个github页面的这个:

这是使用 es6 spread 的一种更短的方法,类似于 renaudtertrais's - 但使用 es6 而不是添加到原型中。

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

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

flatMap(arr, s)

这两个有帮助吗?

应该注意(感谢@ftor),如果在一个非常大的(例如,300k 个元素)数组上调用,后一种“解决方案”会受到“超出最大调用堆栈大小”的影响a

谢谢。我为此效果添加了注释。
2021-04-25 20:19:34
澄清一下,如果我理解正确的话,@ftor,你说的是我提供的链接中的递归实现,而不是我在答案末尾的代码块中显示的递归实现,对吗?
2021-04-29 20:19:34
这种实现可能会炸毁大型阵列的堆栈。
2021-05-04 20:19:34
试试[].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase())))当然,这是一个角落案例。但你至少应该提到它。
2021-05-09 20:19:34

Lodash提供了一个 flatmap 函数,对我来说这实际上相当于 Javascript 原生提供它。如果您不是 Lodash 用户,那么 ES6 的Array.reduce()方法可以为您提供相同的结果,但您必须以离散的步骤进行 map-then-flatten。

下面是每种方法的示例,映射整数列表并仅返回几率。

洛达什:

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

ES6 减少:

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

一种相当简洁的方法是使用Array#concat.apply

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

console.log(flatMap([1, 2, 3], el => [el, el * el]));