FP方式下的嵌套数组映射

6

给定以下数组:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

是否有任何ramda函数可以简化以下场景,我可以给其中一个或多个数组?

const nestedMap = map => {
    const result = []

    for(let item1 of array1) 
        for(let item2 of array2)
            for(let item3 of array3)
                    result.push(map(item1, item2, item3))
    return result
}

整个函数如下所示:
// Sample usage
nestedMap((item1, item2, item3) => `${item1} ${item2} ${item3}`, array1, array2, array3)

我希望避免重复造轮子。

注:原生JavaScript或其他库都可以接受。我最初谈论了ramda,因为它有很多函数,也许我错过了可以在解决这个问题上有所帮助的函数


既然你在问函数式编程,那么为什么你的函数没有返回值呢?我这里看不到任何映射操作,这更像是一个带有回调函数的多维forEach。 - Bergi
这被称为笛卡尔积 - Bergi
@Bergi 抱歉,我已经修复了我的问题。现在它已经返回了。 - Matías Fidemraizer
@MatíasFidemraizer 但现在这样做没有任何意义,它只会调用一次map回调函数。我确定你不想要那样。映射函数应该保留结构。你希望结果看起来像什么? - Bergi
@MatíasFidemraizer 是的,你也可以组合一些生成器。或者使用 Ramda 的 concatMap/chain - Bergi
显示剩余4条评论
4个回答

5
你可以在这里使用数组的applicative实例来简单地R.lift你的函数:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

const nestedMap = R.lift((item1, item2, item3) => `${item1} ${item2} ${item3}`)

console.log(nestedMap(array1, array2, array3))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>


那真的很棒。 - Nina Scholz
这就是 答案 :D - Matías Fidemraizer
我有一个问题:数组如何满足FantasyLand的apply规范? - Matías Fidemraizer
没事了,我已经找到了这个 https://dev59.com/FloV5IYBdhLWcg3wN8dD - Matías Fidemraizer

3

Ramda有一个xprod函数,用于给出两个列表的交叉积。将其扩展到多个列表也是相对简单的,如下所示:

const xproduct = R.reduce(R.pipe(R.xprod, R.map(R.unnest)), [[]])

然后,我们可以相对容易地使用这个来创建嵌套的map函数:

const array1 = ["a1", "b1", "c1", "d1"],
      array2 = ["a2", "b2"],
      array3 = ["a3", "b3", "c3"]

const xproduct = R.reduce(R.pipe(R.xprod, R.map(R.unnest)), [[]])
const nestedMap = (fn, ...arrs) => R.map(R.apply(fn), xproduct(arrs))

console.log(nestedMap((a, b, c) => `${a}-${b}-${c}`, array1, array2, array3))
//==> ["a1-a2-a3", "a1-a2-b3", ...]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>


2

你可以采用两步方法:

  1. 构建所有产品
  2. 映射函数。

const
    nestedMap = (fn, ...array) => array
        .reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), []))
        .map(a => fn(...a)),
    array1 = ["a1", "b1", "c1", "d1"],
    array2 = ["a2", "b2"],
    array3 = ["a3", "b3", "c3"],
    result = nestedMap((item1, item2, item3) => `${item1} ${item2} ${item3}`, array1, array2, array3)

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }


2

工作原理

本回答通过在Array的原型上实现.ap(以及.chain)来展示@ScottChristopher的回答是如何工作的 - 作为一个练习!不要惊慌,原型狂热者...

这里的想法是展示一个代码段,显示您需要了解的所有移动部件的目标行为/输出。只需了解约8行代码,恐吓因素非常低;与挖掘Rambda源码之类的东西相比(实际上非常好),这很容易懂。

我最近分享过另一个答案,它使用分隔的连续体做了非常相似的事情 - 如果您对此答案感兴趣,我认为您也会喜欢那篇文章 ^_^

// Array Applicative  
Array.prototype.ap = function (...args)
  {
    const loop = (acc, [x,...xs]) =>
      x === undefined
        ? [ this [0] (...acc) ]
        : x.chain (a =>
            loop (acc.concat ([a]), xs))
    return loop ([], args)
  }
 
// Array Monad
Array.prototype.chain = function chain (f)
  {
    return this.reduce ((acc, x) =>
      acc.concat (f (x)), [])
  }

const array1 = ['a1', 'b1', 'c1', 'd1']
const array2 = ['a2', 'b2']
const array3 = ['a3', 'b3', 'c3']

console.log ([ (x,y,z) => [x,y,z] ] .ap (array1, array2, array3))
// [ [ 'a1', 'a2', 'a3' ],
//   [ 'a1', 'a2', 'b3' ],
//   [ 'a1', 'a2', 'c3' ],
//   [ 'a1', 'b2', 'a3' ],
//   [ 'a1', 'b2', 'b3' ],
//   [ 'a1', 'b2', 'c3' ],
//   [ 'b1', 'a2', 'a3' ],
//   [ 'b1', 'a2', 'b3' ],
//   [ 'b1', 'a2', 'c3' ],
//   [ 'b1', 'b2', 'a3' ],
//   [ 'b1', 'b2', 'b3' ],
//   [ 'b1', 'b2', 'c3' ],
//   [ 'c1', 'a2', 'a3' ],
//   [ 'c1', 'a2', 'b3' ],
//   [ 'c1', 'a2', 'c3' ],
//   [ 'c1', 'b2', 'a3' ],
//   [ 'c1', 'b2', 'b3' ],
//   [ 'c1', 'b2', 'c3' ],
//   [ 'd1', 'a2', 'a3' ],
//   [ 'd1', 'a2', 'b3' ],
//   [ 'd1', 'a2', 'c3' ],
//   [ 'd1', 'b2', 'a3' ],
//   [ 'd1', 'b2', 'b3' ],
//   [ 'd1', 'b2', 'c3' ] ]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接