2009-07-21 56 views
30

(在多个SEQ映射)假设我有zipWith Scala中

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。也许流解决懒惰的问题,但无论如何,这感觉像不必要的丑陋。在lisp中,映射函数可以映射多个序列。我会写

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

并没有临时列表将创建任何地方。在Scala中是否有一个map-over-multiple-lists函数,还是zip与解构结合在一起真的是“正确”的方法?

回答

15

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

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

请参阅this Trac ticket

+2

对不起,在Scala 2.8上没有zipWith。 – 2009-12-02 12:21:04

+3

为了清楚(我敢肯定Daniel会同意),Scala没有什么可以为此道歉的 - 你用Scala得到的更好。请参阅下面的Martin的答案和Daniel的答案。如果有人能让马丁成为这个问题的核准答案,那将是非常好的...... – AmigoNico 2012-08-12 04:56:22

3

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

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

更新:要清楚,您需要使用流(懒惰列表)而不是列表。斯卡拉的流有一个拉链工作的懒惰的方式,所以你不应该把东西转换成列表。

理想的情况下你的算法应该能够在两个无限流工作没有吹起来的(假设它不会做任何folding,当然,只是读取并生成流)。

+0

我知道什么是懒惰列表,但我不太熟悉Scala。 foo.toList不是懒惰的,对吧?无论如何,来自CL背景的感觉非常奇怪,因为没有mapMultiple函数,所以我提出这个问题的理由就是弄清楚Scala正确的做法是什么。 表现其实相当重要;这是在我的内部循环中,虽然我可以尝试稍后进行优化,但我希望先以合理的方式对其进行编码。 – bsdfish 2009-07-21 06:43:13

+0

我告诉你,当你说“也许流解决问题”时,你是正确的 - 使用流的zip版本。如果你认为小的分配会给GC带来压力,那么在你选择的基于JVM的语言中编写一个命令式的等价物,并且让它们看看它是否是真的(我经常为处理大量虚拟机的杰出人物感到惊讶小短暂分配)。 – 2009-07-21 07:02:04

9

那么,缺少zip,是Scala的2.7 Seq中的一个缺陷。 Scala 2.8采用了精心设计的收集设计,以取代2.7中收藏的临时方式(注意它们并非都是同时创建的,具有统一的设计)。

现在,当你想避免创建临时集合时,你应该在Scala 2.7上使用“投影”,或者在Scala 2.8上使用“视图”。这会给你一个集合类型,其中某些指令,特别是map,flatMap和filter是非严格的。在Scala 2.7上,List的投影是一个Stream。在Scala 2.8中,有一个序列视图,但序列中有一个zipWith,你甚至不需要它。如前所述,JVM经过优化,可以处理临时对象分配,并且在服务器模式下运行时,运行时优化可以创造奇迹。所以,不要过早优化。在将要运行的条件下测试代码 - 如果您还没有计划在服务器模式下运行代码,那么请重新考虑一下代码是否需要长时间运行,以及何时/何地/如果有必要,优化代码。

编辑

什么是真正将是可在斯卡拉2.8是这样的:

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

UPDATE:它已经指出(在评论),这个 “答案” 没有按实际上并没有解决被问到的问题。这个回答将映射在每组合foobar,产生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 { ... }语法真的是做到这一点的最惯用的方式。您可以根据需要继续添加生成器子句(例如b <- bar)。因此,如果它突然变为三个Seq,您可以轻松地根据您的要求缩放您的语法(以产生一个短语)。

+4

我现在不会投下这个问题,但这是完全错误的。这将导致N×N个元素,并且只有N个元素要求结果。您正在添加foo和bar的每个元素组合,但要求的是foo(i)+ bar(i)。 – 2009-07-21 14:18:23

+1

好点。早上有点早,所以显然我的大脑不能正常工作。我将删除这个答案,因为它实际上不提供发件人的要求。 – 2009-07-21 15:43:11

+1

其实,我只是更新答案。这是很好的信息,只是不适用于这个问题。 – 2009-07-21 15:44:03

74

在斯卡拉2.8:

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

而且它适用于以同样的方式两个以上操作数。即那么你可以与跟进:

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

但是,它似乎不能用于三个以上的操作数。那是对的吗? – Debilski 2010-06-18 21:46:44

+14

正确,'zipped'仅在'Tuple2'和'Tuple3'上定义。抽象概念是Scala(和其他大多数静态类型语言)的最终前沿之一。 HLists提供了一种可能性...... – retronym 2010-06-19 23:00:22

+7

@retronym也有我们在Haskell中使用ZipLists所采用的'<*>'/'<$>'方法,在那里您并不需要由于curried函数的同质性而抽象化。因此,如果我想用5参数'f'进行拉链,我可以做更多或更少的操作。不幸的是,curry函数在Scala中处理起来更加痛苦:或许有些想法可能会被延续,因为这种方法似乎比'HList'更优雅。 – 2014-02-14 20:14:53

1

当面对类似的任务,我增加了以下皮条客到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]other收集与快速tailhead

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