映射时如何合并两个数组?

时间:2018-09-09 02:43:13

标签: functional-programming ramda.js

背景:我是Ramda和FP的新手。我发现自己遇到了这种情况,其中有两个输入itemListcostList。列表中的值具有基于idx的关系。因此itemList[0]costList[0]表示可能在同一对象中的值(例如{ item: itemList[0], cost: costList[0]})。因此,替代解决方案可以从合并itemList和costList开始。我对这两种解决方案都感兴趣。任何其他提示和建议也将不胜感激。

  let itemList = ['shirt', 'shoes'];
  let costList = ['shirtCost', 'shoesCost'];

  let formulaList = R.map(
    x => ({
      formulaXAttr: x,
      formulaYAttr: 'tbd later',
    })
  )(itemList);

  let finalList = R.map(
    x => R.merge(x, {formulaYAttr: '???'})  // how to merge in costList?
  )(formulaList);

  // this is how i'd do it in vanilla JS
  let finalList = formulaList.map((x, idx) => ({
    ...x,
    formulaYAttr: costList[idx],
  }));

3 个答案:

答案 0 :(得分:3)

您要在Ramda中寻找的函数称为zipWith,该函数接受一个预期有两个参数的函数,一个用于第一个列表的每个元素,以及一个来自第二个列表的成对元素。最终得到一个列表,该列表与所提供的两个列表中的较短者一样长,其中包含每对调用该函数的结果。

const itemList = ['shirt', 'shoes'];
const costList = ['shirtCost', 'shoesCost'];

const fn = R.zipWith((x, y) =>
  ({ formulaXAttr: x, formulaYAttr: y }))

console.log(
  fn(itemList, costList)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

答案 1 :(得分:2)

斯科特·克里斯托弗的答案显然是正确的。 zipWith专为这种情况而设计。在标题中,您询问在映射时如何执行此操作。我想指出的是,尽管Ramda确实为此提供了一个选项(有关详细信息,请参见addIndex),但通常对此并不满意。这样做的原因对于深入了解FP非常重要。

map的一种理解是,通过将给定函数应用于每个元素,它可以将一种类型的元素列表转换为另一种类型的元素列表。这是一个很好的表述,但是有一个更通用的表述:map通过以下方式将一种类型的元素的容器转换为另一种类型的元素的 container 给定功能在每个元素上的应用。换句话说,mapping的概念可以应用于许多不同的容器,而不仅仅是列表。 FantatsyLand规范为具有map方法的容器定义了一些规则。特定类型为Functor,但是对于没有相关数学背景的人,可以将其视为Mappable。 Ramda应该与以下类型很好地互操作:

const square = n => n * n;
map(square, [1, 2, 3, 4, 5])  //=> [1, 4, 9, 16, 25]


// But also, if MyContainer is a Functor
map(square, MyContainer(7)) //=> MyContainer(49)


// and, for example, if for some Maybe Functor with Just and Nothing subtypes
const {Just, Nothing} = {Maybe}
// then
map(square, Just(6)) //=> Just(36)
map(square, Nothing()) //=> Nothing()

Ramda的map函数仅使用容器的元素来调用提供的转换函数。它不提供索引。这是有道理的,因为并非所有容器都具有索引概念。

因此,映射Ramda并不是尝试匹配索引的地方。但这是Ramda的three zip functions的核心。如果要合并两个元素与索引匹配的列表,则可以像Scott对zipWith所做的那样非常简单。但您也可以使用zip,其工作原理如下:

zip(['a', 'b', 'c', 'd'], [2, 3, 5, 7]) //=> [['a', 2], ['b', 3], ['c', 5], ['d', 7]]

随后在结果对上调用mapzipWith只需一步即可完成。但是如果没有它,像mapzip这样的原语仍然可以组合起来做自己喜欢的事。

答案 2 :(得分:2)

在这里与其他答案一起讨论时,我想向您展示如何自行编写此类内容。 Ramda库并不总是为您提供一站式解决方案,如果您将解决方案限制为仅使用Ramda提供的功能,则会使您退缩。

这种做法将使您有信心尝试编写自己的程序。当/如果发现Ramda具有所需的某些功能,则可以轻松地重构程序以使用Ramda提供的功能。

const None =
  Symbol ()

const zipWith = (f, [ x = None, ...xs ], [ y = None, ...ys ]) =>
  x === None || y === None
    ? []
    : [ f (x, y) ] .concat (zipWith (f, xs, ys))

console.log
  ( zipWith
      ( (name, price) => ({ name, price })
      , [ 'shoes', 'pants' ]
      , [ 19.99, 29.99 ]
      )
  )

// [ { name: "shoes", price: 19.99 }
// , { name: "pants", price: 29.99 }
// ]

上面使用的解构分配创建有助于维护声明式样式,但是它确实创建了不必要的中间值。下面,我们使用附加的状态参数i来显着减少内存占用。

const zipWith = (f, xs, ys, i = 0) =>
  i >= xs.length || i >= ys.length
    ? []
    : [ f (xs[i], ys[i]) ] .concat (zipWith (f, xs, ys, i + 1))
    
console.log
  ( zipWith
      ( (name, price) => ({ name, price })
      , [ 'shoes', 'pants' ]
      , [ 19.99, 29.99 ]
      )
  )
  
// [ { name: "shoes", price: 19.99 }
// , { name: "pants", price: 29.99 }
// ]

请注意,两种实现都是纯的。没有副作用。输入不会发生突变,并且总是为输出构造一个新的Array。

我们的实现也是全面的,因为当提供有效输入时,它将始终以有效输出作为响应。

console.log
  ( zipWith
      ( (name, price) => ({ name, price })
      , [ 'shoes' ]
      , [ 19.99 ]
      )
      // [ { name: "shoes", price: 19.99 } ]

  , zipWith
      ( (name, price) => ({ name, price })
      , [ 'shoes' ]
      , []
      )
      // []

  , zipWith
      ( (name, price) => ({ name, price })
      , []
      , [ 19.99 ]
      )
      // []

  , zipWith
      ( (name, price) => ({ name, price })
      , []
      , []
      )
      // []
  )

这次,Ramda支持R.zipWith,但下次可能不会。但是不要害怕,因为您得到了!一旦意识到编程中没有魔术,编写此类过程将变得很容易。而且,如果您遇到问题,我想总是会有StackOverflow。