Melhor maneira de mesclar dois mapas e sumr os valores da mesma chave?

val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) 

Eu quero mesclá-los e sumr os valores das mesmas chaves. Então o resultado será:

 Map(2->20, 1->109, 3->300) 

Agora tenho 2 soluções:

 val list = map1.toList ++ map2.toList val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum } 

e

 val merged = (map1 /: map2) { case (map, (k,v)) => map + ( k -> (v + map.getOrElse(k, 0)) ) } 

Mas quero saber se existem soluções melhores.

   

Scalaz tem o conceito de um semigrupo que capta o que você quer fazer aqui e leva a uma solução mais curta / mais limpa:

 scala> import scalaz._ import scalaz._ scala> import Scalaz._ import Scalaz._ scala> val map1 = Map(1 -> 9 , 2 -> 20) map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 9, 2 -> 20) scala> val map2 = Map(1 -> 100, 3 -> 300) map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 100, 3 -> 300) scala> map1 |+| map2 res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 109, 3 -> 300, 2 -> 20) 

Especificamente, o operador binário para Map[K, V] combina as chaves dos mapas, dobrando o operador de semigrupos de V sobre quaisquer valores duplicados. O semigrupo padrão para Int usa o operador de adição, portanto, você obtém a sum dos valores para cada chave duplicada.

Edit : Um pouco mais detalhadamente, conforme o pedido do user482745.

Matematicamente, um semigrupo é apenas um conjunto de valores, junto com um operador que recebe dois valores desse conjunto e produz outro valor desse conjunto. Então, inteiros sob adição são um semigrupo, por exemplo – o operador + combina dois inteiros para fazer outro int.

Você também pode definir um semigrupo sobre o conjunto de “todos os mapas com um determinado tipo de chave e tipo de valor”, desde que você possa criar alguma operação que combine dois mapas para produzir um novo que seja de alguma forma a combinação dos dois. inputs.

Se não houver chaves que aparecem nos dois mapas, isso é trivial. Se a mesma chave existe em ambos os mapas, então precisamos combinar os dois valores para os quais a chave mapeia. Hmm, não acabamos de descrever um operador que combina duas entidades do mesmo tipo? É por isso que em Scalaz um semigrupo para Map[K, V] existe se e somente se existe um Semigroup para V – o semigrupo de V é usado para combinar os valores de dois mapas que são atribuídos à mesma chave.

Então, porque Int é o tipo de valor aqui, a “colisão” na chave 1 é resolvida pela adição de inteiros dos dois valores mapeados (como é o que faz o operador de semigrupos do Int), portanto 100 + 9 . Se os valores tivessem sido Strings, uma colisão teria resultado na concatenação de string dos dois valores mapeados (novamente, porque é isso que o operador de semigrupo para String faz).

(E, curiosamente, porque a concatenação de strings não é comutativa – isto é, "a" + "b" != "b" + "a" – a operação de semigrupo resultante também não é. Então map1 |+| map2 é diferente de map2 |+| map1 no caso String, mas não no caso Int.)

A resposta mais curta que eu conheço que usa apenas a biblioteca padrão é

 map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) } 

Solução rápida:

 (map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap 

Bem, agora na biblioteca scala (pelo menos em 2.10) há algo que você queria – function mesclada . MAS é apresentado apenas no HashMap não no mapa. É um pouco confuso. Também a assinatura é incômoda – não posso imaginar por que eu precisaria de uma chave duas vezes e quando eu precisaria produzir um par com outra chave. Mas, no entanto, funciona e muito mais limpo do que as soluções “nativas” anteriores.

 val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12) val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12) map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) }) 

Também em scaladoc mencionou que

O método merged tem, em média, mais desempenho do que fazer um percurso e reconstruir um novo mapa de hash imutável a partir do zero, ou ++ .

Isso pode ser implementado como um Monoid com apenas Scala simples. Aqui está uma implementação de amostra. Com essa abordagem, podemos mesclar não apenas 2, mas uma lista de mapas.

 // Monoid trait trait Monoid[M] { def zero: M def op(a: M, b: M): M } 

A implementação baseada em mapas do traço Monóide que mescla dois mapas.

 val mapMonoid = new Monoid[Map[Int, Int]] { override def zero: Map[Int, Int] = Map() override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] = (a.keySet ++ b.keySet) map { k => (k, a.getOrElse(k, 0) + b.getOrElse(k, 0)) } toMap } 

Agora, se você tem uma lista de mapas que precisam ser mesclados (neste caso, apenas 2), isso pode ser feito como abaixo.

 val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) val maps = List(map1, map2) // The list can have more maps. val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op) 

Eu escrevi um post sobre isso, confira:

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

basicamente usando scalaz semi group você pode conseguir isso facilmente

seria algo como:

  import scalaz.Scalaz._ map1 |+| map2 
 map1 ++ ( for ( (k,v) < - map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) ) 

A resposta de Andrzej Doyle contém uma ótima explicação de semigrupos que permite usar o |+| operador para juntar dois mapas e sumr os valores para as chaves correspondentes.

Há muitas maneiras pelas quais algo pode ser definido como uma instância de um typeclass e, ao contrário do OP, talvez você não queira sumr suas chaves especificamente. Ou talvez você queira operar em um sindicato em vez de em um cruzamento. O Scalaz também adiciona funções extras ao Map para esse propósito:

https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/ index.html # scalaz.std.MapFunctions

Você pode fazer

 import scalaz.Scalaz._ map1 |+| map2 // As per other answers map1.intersectWith(map2)(_ + _) // Do things other than sum the values 

Isto é o que eu inventei …

 def mergeMap(m1: Map[Char, Int], m2: Map[Char, Int]): Map[Char, Int] = { var map : Map[Char, Int] = Map[Char, Int]() ++ m1 for(p < - m2) { map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0))) } map } 

Você também pode fazer isso com gatos .

 import cats.implicits._ val map1 = Map(1 -> 9 , 2 -> 20) val map2 = Map(1 -> 100, 3 -> 300) map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300) 

Eu tenho uma pequena function para fazer o trabalho, é na minha pequena biblioteca para algumas funcionalidades usadas com freqüência que não está na biblioteca padrão. Deve funcionar para todos os tipos de mapas, mutáveis ​​e imutáveis, não apenas HashMaps

Aqui é o uso

 scala> import com.daodecode.scalax.collection.extensions._ scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _) merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4) 

https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith

E aqui está o corpo

 def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr = if (another.isEmpty) mapLike.asInstanceOf[Repr] else { val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr]) another.foreach { case (k, v) => mapLike.get(k) match { case Some(ev) => mapBuilder += k -> f(ev, v) case _ => mapBuilder += k -> v } } mapBuilder.result() } 

https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190

A maneira mais rápida e simples:

 val m1 = Map(1 -> 1.0, 3 -> 3.0, 5 -> 5.2) val m2 = Map(0 -> 10.0, 3 -> 3.0) val merged = (m2 foldLeft m1) ( (acc, v) => acc + (v._1 -> (v._2 + acc.getOrElse(v._1, 0.0))) ) 

Desta forma, cada elemento é imediatamente adicionado ao mapa.

A segunda maneira é:

 map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) } 

Diferentemente da primeira maneira, em uma segunda maneira para cada elemento em um segundo mapa, uma nova lista será criada e concatenada ao mapa anterior.

A expressão case cria implicitamente uma nova List usando o método unapply .