zipWith Scala中(在多个SEQ映射)
-
18-09-2019 - |
题
假设我有
val foo : Seq[Double] = ...
val bar : Seq[Double] = ...
和我希望以产生SEQ其中巴兹(ⅰ)= FOO(ⅰ)+巴(i)中。我能想到的一种方式来做到这一点。
val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b)
然而,这种感觉既丑陋和低效 - 我要两到seqs列表(与惰性列表爆炸)转换,创建的元组这个临时列表中,只有映射了它,并让它成为GCed。也许流解决偷懒的问题,但在任何情况下,这种感觉就像不必要的丑陋。在口齿不清,地图功能将多个序列映射结束。我会写
(mapcar (lambda (f b) (+ f b)) foo bar)
和没有临时列表中会随时随地创建。有Scala中的一个地图,超多列表功能,或拆解的拉链结合真的是“正确”的方式做到这一点?
解决方案
你想要的功能被称为zipWith
,但它不是标准库的一部分。这将是在2.8。(UPDATE:显然不能,见注释)
foo zipWith((f: Double, b : Double) => f+b) bar
请参阅此Trac的票。
其他提示
在Scala的2.8:
val baz = (foo, bar).zipped map (_ + _)
和它适用于以同样的方式两个以上操作数。即那么你可以与跟进:
(foo, bar, baz).zipped map (_ * _ * _)
好,即,缺乏拉链的,是在Scala的2.7 SEQ缺乏。斯卡拉2.8有一个深思熟虑的集设计,更换特别的方式出现在2.7集合走过来的(请注意,他们没有一次全部建立,有统一的设计)。
现在,当您想避免创建临时集合,你应该在斯卡拉2.8使用“投影”上的Scala 2.7,或“查看”。这会给你一个集合类型,其中某些指令,特别是地图,flatMap和过滤,是不严谨的。在斯卡拉2.7,列表的投影流。在斯卡拉2.8,还有一个序列的SequenceView,但有一个zipWith正确的,在序列,你甚至不会需要它。
话虽如此,如前所述,JVM经优化以处置临时对象分配,并且,在服务器模式下运行时,运行时优化可以奇迹。所以,不要过早优化。试验将运行条件的代码 - 如果你还没有计划在服务器模式下运行它,然后重新考虑,如果代码预计将长时间运行,和租期,提高何时/何/如果有必要<。 / p>
修改强>
什么是真正将是可在斯卡拉2.8是这样的:
(foo,bar).zipped.map(_+_)
一个懒惰列表不是一个列表的副本 - 它更像一个单独的对象。在一个慵懒的拉链实现的情况下,每次被问下一个项目时,它从两个输入列表中抓住一个项目,从他们创建一个元组,你再与模式匹配在打散元组您的拉姆达。
因此从来需要开始对它们进行操作之前创建整个输入列表(S)的完整副本。它归结为一个非常类似的分配模式在JVM上运行的任何应用 - 大量非常短暂但小分配的,其JVM被优化来处理
更新:要清楚,你需要使用流(惰性列表)不会列出。 Scala的流有工作偷懒的方法拉链,所以你不应该将东西放进名单。
在理想情况下你的算法应该是能够在两个工作中的无限的流而不炸毁(假设它不会做任何folding
,当然,只是读取并生成流)。
更新:据指出,(在评论),这个“答案”并没有真正解决所提出的问题。这个回答将映射在每组合foo
和bar
,产生的 n×m个的 的元素,而不是分钟(M,N)的要求。所以,这是的错误的,但为后人留下了,因为这是很好的信息。
要做到这一点,最好的方法是用与flatMap
组合map
。码事实胜于雄辩:
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,你必须映射过来,你可以轻松地扩展语法与你的要求(可套用一句话)一起。
当面临着类似的任务,添加以下皮条客到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
传递的集合类型,因为tail
和head
公告将反复调用它。所以,理想情况下,你应该通过Iterable[List]
或其他收集与快速tail
和head
。
此外,该代码期望相同尺寸的嵌套集合。这是我的使用情况,但我怀疑这是可以改善,如果需要的话。