val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
Quero mesclá-los e somar os valores das mesmas chaves. Então o resultado será:
Map(2->20, 1->109, 3->300)
Agora eu 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.
map1 ++ map2
Respostas:
O Scalaz tem o conceito de um semigrupo que captura o que você quer fazer aqui e leva à possível solução mais curta / limpa:
Especificamente, o operador binário para
Map[K, V]
combina as chaves dos mapas, dobrandoV
o operador do semigrupo sobre quaisquer valores duplicados. O semigrupo padrão paraInt
usa o operador de adição, para que você obtenha a soma dos valores para cada chave duplicada.Editar : um pouco mais detalhadamente, conforme a solicitação do usuário482745.
Matematicamente, um semigrupo é apenas um conjunto de valores, junto com um operador que pega dois valores desse conjunto e produz outro valor a partir desse conjunto. Portanto, números inteiros em adição são um semigrupo, por exemplo - o
+
operador combina duas entradas para criar outra 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 seja possível criar alguma operação que combine dois mapas para produzir um novo, que seja de alguma forma a combinação dos dois entradas.
Se não houver teclas que apareçam nos dois mapas, isso é trivial. Se a mesma chave existir nos dois mapas, precisamos combinar os dois valores para os quais a chave é mapeada. Hmm, não acabamos de descrever um operador que combina duas entidades do mesmo tipo? É por isso que no Scalaz
Map[K, V]
existe um semigrupo para se, e somente se, um semigrupo paraV
existir -V
é usado para combinar os valores de dois mapas atribuídos à mesma chave.Portanto, como
Int
é o tipo de valor aqui, a "colisão" na1
chave é resolvida pela adição inteira dos dois valores mapeados (como é o que o operador de semigrupo do Int faz), portanto100 + 9
. Se os valores tivessem sido Strings, uma colisão resultaria 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 - ou seja,
"a" + "b" != "b" + "a"
- a operação de semigrupo resultante também não é. Portanto,map1 |+| map2
é diferente domap2 |+| map1
caso String, mas não no caso Int).fonte
scalaz
fazia sentido.A
eOption[A]
) é tão grande que eu não podia acreditar que eles eram realmente do mesmo tipo. Eu apenas comecei a olhar para Scalaz. Não tenho certeza de que sou inteligente o suficiente ...A resposta mais curta que conheço que usa apenas a biblioteca padrão é
fonte
++
substitui qualquer (k, v) do mapa no lado esquerdo de++
(aqui map1) por (k, v) do mapa do lado direito, se (k, _) já existir no lado esquerdo mapa lateral (aqui map1), por exemploMap(1->1) ++ Map(1->2) results in Map(1->2)
for
map1 ++ (para ((k, v) <- map2) produz k -> (v + map1.getOrElse (k, 0 ))).
tem precedência mais alta que++
; você lêmap1 ++ map2.map{...}
comomap1 ++ (map2 map {...})
. Então, de um jeito que você mapeia osmap1
elementos de s, e do outro, não.Solução rápida:
fonte
Bem, agora na biblioteca scala (pelo menos na 2.10) há algo que você queria - função mesclada . MAS é apresentado apenas no HashMap e não no Mapa. É um pouco confuso. Além disso, a assinatura é complicada - não consigo imaginar por que eu precisaria de uma chave duas vezes e quando precisaria produzir um par com outra chave. Porém, ele funciona e é muito mais limpo que as soluções "nativas" anteriores.
Também no scaladoc mencionou que
fonte
MergeFunction
.private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
Isso pode ser implementado como um Monoid com Scala simples. Aqui está uma implementação de amostra. Com essa abordagem, podemos mesclar não apenas 2, mas uma lista de mapas.
A implementação baseada no mapa da característica Monoid que mescla dois mapas.
Agora, se você tiver uma lista de mapas que precisam ser mesclados (neste caso, apenas 2), isso pode ser feito como abaixo.
fonte
fonte
Eu escrevi um post sobre isso, confira:
http://www.nimrodstech.com/scala-map-merge/
basicamente usando semi grupo scalaz, você pode conseguir isso facilmente
seria algo como:
fonte
Você também pode fazer isso com gatos .
fonte
import cats.implicits._
. Importarimport cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._
não muito mais detalhado ...import cats.implicits._
Iniciando
Scala 2.13
, outra solução baseada apenas na biblioteca padrão consiste em substituir agroupBy
parte da sua solução pelagroupMapReduce
qual (como o nome sugere) é equivalente a uma etapagroupBy
seguida pormapValues
e uma etapa de redução:Este:
Concatena os dois mapas como uma sequência de tuplas (
List((1,9), (2,20), (1,100), (3,300))
). Por motivos de concisão,map2
é implicitamente convertido emSeq
para se adaptar ao tipo demap1.toSeq
- mas você pode optar por torná-lo explícito usandomap2.toSeq
,group
s elementos baseados na primeira parte da tupla (parte do grupo MapReduce),map
s agruparam valores na segunda parte da tupla (mapear parte do grupo Map Reduce),reduce
s valores mapeados (_+_
) somando-os (reduza parte do groupMap Reduce ).fonte
Aqui está o que eu acabei usando:
fonte
A resposta de Andrzej Doyle contém uma ótima explicação dos semigrupos, que permite usar o
|+|
operador para unir dois mapas e somar os valores das chaves correspondentes.Há muitas maneiras pelas quais algo pode ser definido para ser uma instância de uma classe de tipo e, diferentemente do OP, você pode não querer somar suas chaves especificamente. Ou, talvez você queira operar em uma união e não em um cruzamento. O Scalaz também adiciona funções extras
Map
para este fim: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
fonte
A maneira mais rápida e simples:
Dessa forma, cada elemento é adicionado imediatamente ao mapa.
A segunda
++
maneira é: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
case
expressão cria implicitamente uma nova lista usando ounapply
métodofonte
Isto é o que eu vim com ...
fonte
Usando o padrão tipeclass, podemos mesclar qualquer tipo numérico:
Uso:
Mesclando uma sequência de mapas:
fonte
Eu tenho uma função pequena para fazer o trabalho, é na minha pequena biblioteca algumas funcionalidades frequentemente usadas que não estão na biblioteca padrão. Deve funcionar para todos os tipos de mapas, mutáveis e imutáveis, não apenas HashMaps
Aqui está o uso
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
E aqui está o corpo
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fextensions%2Fpackage.scala#L190
fonte