2009-08-11 96 views
28

我有一個Map [String,Double]列表,我想將它們的內容合併到一個Map [String,Double]中。我應該如何用慣用的方式來做到這一點?我想我應該能夠做到這一點。類似於:Scala:如何合併地圖集合

val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... } 

此外,我想以通用方式處理關鍵衝突。也就是說,如果我將一個鍵添加到已存在的映射中,我應該能夠指定一個返回Double的函數(在這種情況下),並將該鍵的現有值加上我試圖添加的值。如果密鑰尚未存在於地圖中,則只需添加它,並且其值不變。

在我的具體情況下,我想建立一個Map [String,Double],如果地圖已經包含一個鍵,那麼Double將被添加到現有的地圖值。

我正在使用特定代碼中的可變映射,但如果可能的話,我對更通用的解決方案感興趣。

回答

23

這個怎麼樣:

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] = 
    (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) => 
    a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv) 
    } 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
val mm = mergeMap(ms)((v1, v2) => v1 + v2) 

println(mm) // prints Map(hello -> 5.5, world -> 2.2, goodbye -> 3.3) 

而且這兩個2.7.5和2.8.0的作品。

+0

這正是我最初想要做的。我不認爲要在那裏放置理解 - 我仍然習慣於像這樣使用它們,但它是有道理的。在這種情況下,我可以看到它與Python的列表解析非常相似,我對此更爲自在。也就像在調用。+()內部的表達式中使用result-bearing一樣。 – Jeff 2009-08-12 19:04:53

+0

乾淨的答案。榮譽 – 2017-09-22 17:25:41

37

嗯,你可以這樣做:

mapList reduce (_ ++ _) 

除了碰撞的特殊要求。

既然你有特殊的要求,也許最好的是做這樣的事情(2.8):

def combine(m1: Map, m2: Map): Map = { 
    val k1 = Set(m1.keysIterator.toList: _*) 
    val k2 = Set(m2.keysIterator.toList: _*) 
    val intersection = k1 & k2 

    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key))) 
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
    r2 ++ r1 
} 

可以,則此方法通過皮條客我的圖書館模式添加到地圖類,並使用它在原來的例子,而不是「++」:

class CombiningMap(m1: Map[Symbol, Double]) { 
    def combine(m2: Map[Symbol, Double]) = { 
    val k1 = Set(m1.keysIterator.toList: _*) 
    val k2 = Set(m2.keysIterator.toList: _*) 
    val intersection = k1 & k2 
    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key))) 
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
    r2 ++ r1 
    } 
} 

// Then use this: 
implicit def toCombining(m: Map[Symbol, Double]) = new CombiningMap(m) 

// And finish with: 
mapList reduce (_ combine _) 

雖然這是寫在2.8,所以keysIterator成爲keys 2.7,filterKeys可能需要在方面來寫和map,&變成**等等,它不應該太差。

+1

有點兒違背了點忽略這一要求。 – Jeff 2009-08-11 21:58:44

+0

這就是爲什麼我擴展它。 – 2009-08-11 22:00:23

+0

隨着現代斯卡拉:val k1 = m1.keysIterator.toSet – qerub 2012-11-22 18:07:30

2

有趣的是,這個有點繞noodling,我得到了以下(上2.7.5):

一般地圖:

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: Seq[scala.collection.Map[A,B]]): Map[A, B] = { 
    listOfMaps.foldLeft(Map[A, B]()) { (m, s) => 
     Map(
     s.projection.map { pair => 
     if (m contains pair._1) 
      (pair._1, collisionFunc(m(pair._1), pair._2)) 
     else 
      pair 
     }.force.toList:_*) 
    } 
    } 

但是男人,那是可怕的與投影和強迫和toList和whatnot。另外一個問題:在這個範圍內處理這個問題的最好方法是什麼?

對於易變的地圖,這是我在我的代碼處理,並用較少的通用的解決方案,我得到這個:

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A, B] = { 
    listOfMaps.foldLeft(mutable.Map[A,B]()) { 
     (m, s) => 
     for (k <- s.keys) { 
     if (m contains k) 
      m(k) = collisionFunc(m(k), s(k)) 
     else 
      m(k) = s(k) 
     } 
     m 
    } 
    } 

這似乎有點清潔,但只會用可變工作寫入地圖。有趣的是,我首先使用/來代替foldLeft嘗試上面的(在我問這個問題之前),但是我得到了類型錯誤。我認爲/:和foldLeft基本上是等價的,但編譯器不停地抱怨我需要明確的類型(m,s)。那是怎麼回事?

+0

這裏你不需要使用'force',因爲'toList'是嚴格的。 – 2009-08-12 02:17:17

+0

至於'foldLeft' vs'/:',你確實意識到這個對象,而第一個參數是在它們之間交換的?表達式'x foldLeft y'相當於'y /:x'。除此之外,還有一些語法問題。基本上,你*可以寫'(y /:x)(摺疊表達式)',而'foldLeft'可以用作'x.foldLeft(y)(摺疊表達式)'。 – 2009-08-12 02:20:53

+0

是的,我知道以下方法結束的方法:用參數交換對象。這就是我在問題中編寫示例的方式。儘管如此,我確實忘了把y /:x放在parens中,我敢打賭這是一個問題。謝謝! – Jeff 2009-08-12 02:41:21

3

我趕緊閱讀這個問題,所以我不知道如果我失去了一些東西(像它爲2.7.x或沒有scalaz工作):

import scalaz._ 
import Scalaz._ 
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms.reduceLeft(_ |+| _) 
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2) 

您可以更改獨異的定義雙並獲得另一種方式來累積值,及彼最大:

implicit val dbsg: Semigroup[Double] = semigroup((a,b) => math.max(a,b)) 
ms.reduceLeft(_ |+| _) 
// returns Map(goodbye -> 3.3, hello -> 4.4, world -> 2.2) 
+0

+1,雖然我會編寫'ms.suml',它更加簡潔,並且具有不會在空列表上拋出運行時異常的優勢。 – 2012-09-14 01:42:07

+0

@TravisBrown,是的,斯卡拉有這麼多方便的功能;儘管'suml'可能只有7個?我只在6.x中看到'sumr'。 – huynhjl 2012-09-14 07:26:26

0

一個oneliner幫手,FUNC,其使用讀取幾乎一樣使用scalaz清潔:

def mergeMaps[K,V](m1: Map[K,V], m2: Map[K,V])(f: (V,V) => V): Map[K,V] = 
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) }) 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms.reduceLeft(mergeMaps(_,_)(_ + _)) 
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2) 

爲最終可讀性包裝在一個隱含的定製類型:

class MyMap[K,V](m1: Map[K,V]) { 
    def merge(m2: Map[K,V])(f: (V,V) => V) = 
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) }) 
} 
implicit def toMyMap[K,V](m: Map[K,V]) = new MyMap(m) 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms reduceLeft { _.merge(_)(_ + _) } 
2

我寫了一篇博客文章中針對此,檢查出來:

http://www.nimrodstech.com/scala-map-merge/

基本上都採用scalaz半組可以實現這很容易

看起來像這樣:

import scalaz.Scalaz._ 
    listOfMaps reduce(_ |+| _) 
+0

你實際上可以使用'listOfMaps.suml';它應該做同樣的事情。從我所理解的意思是sumLeft,它本質上運行'reduceLeft(_ | + | _)' – JBarber 2015-08-07 16:15:56

17

我很驚訝,沒有人想出這個解決方案尚未:

myListOfMaps.flatten.toMap 

不正是你所需要的:

  1. 融合了列表中單個地圖
  2. 雜草出任何重複鑰匙

例如:

scala> List(Map('a -> 1), Map('b -> 2), Map('c -> 3), Map('a -> 4, 'b -> 5)).flatten.toMap 
res7: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 4, 'b -> 5, 'c -> 3) 

flatten打開的地圖列表成元組的平面列表,toMap輪流元組的名單與所有的重複鍵的地圖取出

+2

這正是我所需要的,但不會按OP要求對重複鍵的值進行求和。 – 2017-02-02 22:09:32

+0

或者你可以使用flatMap – wbmrcb 2017-09-15 04:58:00