假设我有
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(更新:显然不是,见注释)。
foo zipWith((f: Double, b : Double) => f+b) bar
见这Trac的车票 。
在斯卡拉2.8:
val baz = (foo, bar).zipped map (_ + _)
它适用于以同样的方式两个以上操作数。 也就是说,你可以再与跟进:
(foo, bar, baz).zipped map (_ * _ * _)
好吧,那,缺乏拉链,是Scala的2.7 SEQ缺乏。 斯卡拉2.8有一个深思熟虑的集设计,更换特别的方式出现在2.7集合走过来的(请注意,他们没有一次全部建立,有统一的设计)。
现在,当你想避免创建临时集合,你应该使用Scala的上2.7“投影”或“视图”上的Scala 2.8。 这会给你一个集合类型,其中某些指令,特别是地图,flatMap和过滤,是不严谨的。 在斯卡拉2.7,列表的投影流。 在斯卡拉2.8,还有一个序列的SequenceView,但有一个zipWith正确的,在序列,你甚至不会需要它。
话虽如此,如前所述,JVM优化处理临时对象的分配,并在服务器模式下运行时,运行时优化会带来奇迹。 所以,不要过早优化。 试验将运行条件的代码 - 如果你还没有计划在服务器模式下运行它,然后重新考虑,如果代码预计将长时间运行,和租期,提高何时/何/如果需要的话。
编辑
什么是真正将是可在斯卡拉2.8是这样的:
(foo,bar).zipped.map(_+_)
懒列表不是一个列表的副本 - 它更像是一个单独的对象。 在一个慵懒的拉链实现的情况下,每次被问下一个项目时,它从两个输入列表中抓住一个项目,从他们创建一个元组,你再与模式匹配在打散元组您的拉姆达。
所以从来不需要开始对它们进行操作之前创建整个输入列表(S)的完整副本。 它归结为一个非常相似的分配模式,在JVM上运行的任何应用程序 - 大量的非常短暂的,但小拨款,其中JVM优化处理的。
更新:要明确,你需要使用流(惰性列表)不会列出。 Scala的流有工作偷懒的方法拉链,所以你不应该将东西放进名单。
理想的情况是你的算法应该能够在两个无限流工作没有吹起来的(假设它不会做任何folding
,当然,只是读取并生成流)。
当面对类似的任务,我增加了以下皮条客到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
。
此外,此代码预计同样大小的嵌套集合。 这是我的使用情况,但我怀疑这是可以改善,如果需要的话。
更新:它已经指出(在评论),这个“答案”并没有真正解决所提出的问题。 这个回答将每一个组合映射超过foo
和bar
,产生N×M个元素,而不是分钟(M,N)的要求。 所以,这是错误的 ,但为后人留下了,因为这是很好的信息。
要做到这一点,最好的办法是flatMap
联合map
。 代码事实胜于雄辩:
foo flatMap { f => bar map { b => f + b } }
这将产生一个Seq[Double]
,正如你所期望的。 这种模式是如此普遍,斯卡拉实际上包括一些语法的魔术实现它:
for {
f <- foo
b <- bar
} yield f + b
或者,也可以使用:
for (f <- foo; b <- bar) yield f + b
在for { ... }
语法真的是做到这一点的最惯用的方式。 您可以继续添加发电机条款(例如b <- bar
必要)。 因此,如果它突然变为3 Seq
是你们等必须映射过来,你可以很容易地与你的要求一起扩展您的语法(套用一句话)。