在Scala中使用zipWith(映射多个Seq)

时间:2009-07-21 06:29:04

标签: scala functional-programming higher-order-functions

假设我有

val foo : Seq[Double] = ...
val bar : Seq[Double] = ...

我希望产生一个seq,其中baz(i)= foo(i)+ bar(i)。我能想到的一种方法是

val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b)

然而,这感觉既丑又低效 - 我必须将seqs转换为列表(使用惰性列表进行爆炸),创建此临时元组列表,仅映射它并让它进行GCed。也许溪流解决了懒惰的问题,但无论如何,这感觉就像不必要的丑陋。在lisp中,map函数将映射多个序列。我会写

(mapcar (lambda (f b) (+ f b)) foo bar)

并且不会在任何地方创建临时列表。在Scala中是否存在map-over-multiple-lists函数,或者zip是否与解构相结合实际上是“正确”的方法呢?

7 个答案:

答案 0 :(得分:81)

在Scala 2.8中:

val baz = (foo, bar).zipped map (_ + _)

它以同样的方式适用于两个以上的操作数。即然后你可以跟着这个:

(foo, bar, baz).zipped map (_ * _ * _)

答案 1 :(得分:16)

您想要的功能称为zipWith,但它不是标准库的一部分。它将在2.8(更新:显然不是,见评论)。

foo zipWith((f: Double, b : Double) => f+b) bar

请参阅this Trac ticket

答案 2 :(得分:9)

嗯,缺乏拉链,是Scala 2.7 Seq的缺陷。 Scala 2.8拥有精心设计的收藏设计,以取代2.7中收藏品的临时方式(请注意,它们并非都是一次创建,采用统一设计)。

现在,当你想避免创建临时集合时,你应该在Scala 2.7上使用“projection”,或者在Scala 2.8上使用“view”。这将为您提供一种集合类型,其中某些指令(尤其是map,flatMap和filter)是非严格的。在Scala 2.7上,List的投影是一个Stream。在Scala 2.8上,有一个Sequence的SequenceView,但是在Sequence中有一个zipWith,你根本不需要它。

如上所述,JVM经过优化处理临时对象分配,并且在服务器模式下运行时,运行时优化可以创造奇迹。所以,不要过早优化。在将运行的条件下测试代码 - 如果您还没有计划在服务器模式下运行代码,那么重新考虑如果代码需要长时间运行,并在/ where /必要时优化opt。 / p>

修改

Scala 2.8实际上可以使用的是:

(foo,bar).zipped.map(_+_)

答案 3 :(得分:3)

惰性列表不是列表的副本 - 它更像是一个对象。在懒惰的zip实现的情况下,每次要求下一个项目时,它会从两个输入列表中的每一个中获取一个项目并从中创建一个元组,然后使用模式匹配将元组分开。你的lambda。

因此,在开始对它们进行操作之前,永远不需要创建整个输入列表的完整副本。它归结为与JVM上运行的任何应用程序非常相似的分配模式 - 许多非常短暂但很小的分配,JVM经过优化处理。

更新:要明确,您需要使用Streams(懒惰列表)而不是列表。 Scala的流有一个拉链工作的拉链,所以你不应该把东西转换成列表。

理想情况下,您的算法应该能够处理两个无限流而不会爆炸(假设它不执行任何folding,当然,只是读取并生成流)。

答案 4 :(得分:1)

当遇到类似的任务时,我将以下pimp添加到Iterable s:

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) {
  def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] {
    override def iterator: Iterator[V] = new Iterator[V] {
      override def next(): V = {
        val v = f(itemsLeft.map(_.head))
        itemsLeft = itemsLeft.map(_.tail)
        v
      }

      override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty)

      private var itemsLeft = collOfColls
    }
  }
}

有了这个,人们可以这样做:

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
collOfColls.mapZipped { group =>
  group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9)
}

请注意,您应该仔细考虑作为嵌套Iterable传递的集合类型,因为tailhead将被反复调用。因此,理想情况下,您应该使用快速Iterable[List]tail传递headother集合。

此外,此代码需要具有相同大小的嵌套集合。这是我的用例,但我怀疑如果需要可以改进。

答案 5 :(得分:1)

从 Scala 2.13.0 开始

.zipped 已弃用,由 lazyZip 代替。示例代码:

val mangoes = List(1, 2, 3, 4, 5)
val oranges = List(2, 3, 4, 5, 6)
val fruits = (mangoes lazyZip oranges).map((m, o) => m+o)
print(fruits)

印刷品

List(3, 5, 7, 9, 11)

答案 6 :(得分:0)

更新:有人指出(在评论中)这个“答案”实际上并没有解决被问到的问题。此答案将映射foobar的每个组合,生成 N x M 元素,而不是 min(M, N)按要求。所以,这是错误的,但留给了后代,因为它是很好的信息。


执行此操作的最佳方法是flatMapmap相结合。代码胜于雄辩:

foo flatMap { f => bar map { b => f + b } }

这将产生一个Seq[Double],正如您所期望的那样。这种模式非常普遍,以至于Scala实际上包含了一些实现它的语法魔法:

for {
  f <- foo
  b <- bar
} yield f + b

或者,或者:

for (f <- foo; b <- bar) yield f + b

for { ... }语法实际上是最惯用的方法。您可以根据需要继续添加生成器子句(例如b <- bar)。因此,如果它突然变成三个 Seq s,你必须映射,你可以轻松地扩展你的语法和你的要求(硬币短语)。