2009-07-21 25 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

此外,此代碼需要相同大小的嵌套集合。這是我的用例,但我懷疑如果需要的話可以改進。