val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
Quiero fusionarlos y sumar los valores de las mismas claves. Entonces el resultado será:
Map(2->20, 1->109, 3->300)
Ahora tengo 2 soluciones:
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
y
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
Pero quiero saber si hay mejores soluciones.
map1 ++ map2
Respuestas:
Scalaz tiene el concepto de un Semigrupo que captura lo que quiere hacer aquí y conduce a la solución más corta / más limpia:
Específicamente, el operador binario para
Map[K, V]
combina las claves de los mapas, doblandoV
el operador del semigrupo sobre cualquier valor duplicado. El semigrupo estándar paraInt
utiliza el operador de suma, por lo que obtiene la suma de valores para cada clave duplicada.Editar : Un poco más de detalle, según la solicitud del usuario 482745.
Matemáticamente, un semigrupo es solo un conjunto de valores, junto con un operador que toma dos valores de ese conjunto y produce otro valor a partir de ese conjunto. Entonces, los enteros bajo suma son un semigrupo, por ejemplo: el
+
operador combina dos entradas para hacer otro int.También puede definir un semigrupo sobre el conjunto de "todos los mapas con un tipo de clave y un tipo de valor dados", siempre que pueda encontrar alguna operación que combine dos mapas para producir uno nuevo que de alguna manera sea la combinación de los dos entradas
Si no hay claves que aparecen en ambos mapas, esto es trivial. Si existe la misma clave en ambos mapas, entonces necesitamos combinar los dos valores a los que se asigna la clave. Hmm, ¿no acabamos de describir un operador que combina dos entidades del mismo tipo? Es por eso que en Scalaz
Map[K, V]
existe un semigrupo para si y solo siV
existe un semigrupo para -V
el semigrupo se usa para combinar los valores de dos mapas que se asignan a la misma clave.Entonces, como
Int
es el tipo de valor aquí, la "colisión" en la1
clave se resuelve mediante la suma entera de los dos valores mapeados (ya que eso es lo que hace el operador de semigrupo de Int), por lo tanto100 + 9
. Si los valores hubieran sido Strings, una colisión hubiera resultado en la concatenación de los dos valores mapeados (de nuevo, porque eso es lo que hace el operador de semigrupo para String).(Y curiosamente, debido a que la concatenación de cadenas no es conmutativa, es decir,
"a" + "b" != "b" + "a"
la operación de semigrupo resultante tampoco lomap1 |+| map2
es . Por lo tanto, es diferente demap2 |+| map1
en el caso de la cadena, pero no en el caso de Int.)fuente
scalaz
tenía sentido.A
yOption[A]
) es tan grande que no podía creer que fueran realmente del mismo tipo. Yo simplemente empecé a buscar en Scalaz. No estoy seguro de ser lo suficientemente inteligente ...La respuesta más corta que conozco que usa solo la biblioteca estándar es
fuente
++
reemplaza cualquier (k, v) del mapa en el lado izquierdo de++
(aquí mapa1) por (k, v) del mapa del lado derecho, si (k, _) ya existe en el lado izquierdo mapa lateral (aquí mapa1), por ejemploMap(1->1) ++ Map(1->2) results in Map(1->2)
for
mapa1 ++ (para ((k, v) <- map2) produce k -> (v + map1.getOrElse (k, 0 ))).
tiene mayor prioridad que++
; leesmap1 ++ map2.map{...}
comomap1 ++ (map2 map {...})
. Entonces, una forma de mapearmap1
los elementos y la otra no.Solución rápida:
fuente
Bueno, ahora en la biblioteca scala (al menos en 2.10) hay algo que quería: la función fusionada . PERO solo se presenta en HashMap, no en Map. Es algo confuso. Además, la firma es engorrosa: no puedo imaginar por qué necesitaría una clave dos veces y cuándo necesitaría producir un par con otra clave. Sin embargo, funciona y es mucho más limpio que las soluciones "nativas" anteriores.
También en scaladoc mencionó que
fuente
MergeFunction
.private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
Esto se puede implementar como un monoide con Scala simple. Aquí hay una implementación de muestra. Con este enfoque, podemos fusionar no solo 2, sino una lista de mapas.
La implementación basada en mapas del rasgo monoide que combina dos mapas.
Ahora, si tiene una lista de mapas que necesita fusionar (en este caso, solo 2), puede hacerlo como a continuación.
fuente
fuente
Escribí una publicación de blog sobre esto, échale un vistazo:
http://www.nimrodstech.com/scala-map-merge/
Básicamente, usando Scalaz semi group puedes lograr esto fácilmente
se vería algo así como:
fuente
También puedes hacer eso con los gatos .
fuente
import cats.implicits._
. Importarimport cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._
no mucho más detallado ...import cats.implicits._
Comenzando
Scala 2.13
, otra solución basada únicamente en la biblioteca estándar consiste en reemplazar lagroupBy
parte de su solución con lagroupMapReduce
que (como su nombre lo indica) es un equivalente de un pasogroupBy
seguido pormapValues
un paso reducido:Esta:
Concatena los dos mapas como una secuencia de tuplas (
List((1,9), (2,20), (1,100), (3,300))
). Para ser concisos,map2
se convierte implícitamenteSeq
para adaptarse al tipo demap1.toSeq
, pero puede optar por hacerlo explícito utilizandomap2.toSeq
,group
s elementos basados en su primera parte de tupla (parte del grupo del grupo MapReduce),map
s valores agrupados a su segunda parte de tupla (parte del mapa del grupo Map Map Reduce),reduce
s valores asignados (_+_
) al sumarlos (reducir parte de groupMap Reduce ).fuente
Esto es lo que terminé usando:
fuente
La respuesta de Andrzej Doyle contiene una gran explicación de los semigrupos que le permite utilizar el
|+|
operador para unir dos mapas y sumar los valores de las claves coincidentes.Hay muchas formas en que algo puede definirse como una instancia de una clase de tipos, y a diferencia del OP, es posible que no desee sumar sus claves específicamente. O bien, es posible que desee operar en una unión en lugar de una intersección. Scalaz también agrega funciones adicionales
Map
para este 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
Tu puedes hacer
fuente
La forma más rápida y sencilla:
De esta manera, cada uno de los elementos se agrega inmediatamente al mapa.
La segunda
++
forma es:A diferencia de la primera forma, en una segunda forma para cada elemento en un segundo mapa, se creará una nueva Lista y se concatenará con el mapa anterior.
La
case
expresión crea implícitamente una nueva Lista usando elunapply
método.fuente
Esto es lo que se me ocurrió ...
fuente
Usando el patrón typeclass, podemos fusionar cualquier tipo numérico:
Uso:
Fusionar una secuencia de mapas:
fuente
Tengo una pequeña función para hacer el trabajo, está en mi pequeña biblioteca para algunas funciones de uso frecuente que no están en la biblioteca estándar. Debería funcionar para todo tipo de mapas, mutables e inmutables, no solo HashMaps
Aquí está el uso
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
Y aquí está el cuerpo.
https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190
fuente