Meilleure façon de fusionner deux cartes et de résumer les valeurs de la même clé?

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

Je veux les fusionner et additionner les valeurs des mêmes clés. Donc le résultat sera:

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

Maintenant, j’ai 2 solutions:

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

et

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

Mais je veux savoir s’il existe de meilleures solutions.

Scalaz a le concept d’un groupe semigroup qui capture ce que vous voulez faire ici et conduit sans doute à la solution la plus courte / la plus propre:

 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) 

Plus précisément, l’opérateur binary pour Map[K, V] combine les clés des cartes, repliant l’opérateur de semi-groupe de V sur les valeurs en double. Le semi-groupe standard pour Int utilise l’opérateur d’addition, de sorte que vous obtenez la sum des valeurs pour chaque clé dupliquée.

Edit : Un peu plus de détails, selon la demande de user482745.

Mathématiquement, un semigroupe n’est qu’un ensemble de valeurs, associé à un opérateur qui prend deux valeurs de cet ensemble et produit une autre valeur à partir de cet ensemble. Ainsi, les entiers ajoutés sont un semi-groupe, par exemple: l’opérateur + combine deux ints pour créer un autre int.

Vous pouvez également définir un semi-groupe sur l’ensemble de “toutes les cartes avec un type de clé et un type de valeur donnés”, à condition que vous puissiez créer une opération combinant deux cartes pour en créer une nouvelle qui soit en quelque sorte la combinaison des deux. consortingbutions.

S’il n’y a pas de clé dans les deux cartes, c’est sortingvial. Si la même clé existe dans les deux cartes, nous devons combiner les deux valeurs auxquelles la clé correspond. Hmm, n’avons-nous pas simplement décrit un opérateur qui combine deux entités du même type? C’est pourquoi, dans Scalaz, un semigroupe pour Map[K, V] existe si et seulement si un Semigroup pour V existe – le semi-groupe de V est utilisé pour combiner les valeurs de deux cartes affectées à la même clé.

Donc, comme Int est le type de valeur ici, la “collision” sur la clé 1 est résolue par l’ajout d’entiers des deux valeurs mappées (comme le fait l’opérateur de semigroupe Int, donc 100 + 9 . Si les valeurs avaient été Ssortingngs, une collision aurait entraîné la concaténation de chaînes des deux valeurs mappées (encore une fois, car c’est ce que fait l’opérateur de semi-groupe pour Ssortingng).

(Et curieusement, parce que la concaténation de chaînes n’est pas commutative – c’est-à-dire "a" + "b" != "b" + "a" – l’opération semigroup qui en résulte n’est pas non plus. Donc map1 |+| map2 est différent de map2 |+| map1 dans le cas Ssortingng, mais pas dans le cas Int.)

La réponse la plus courte que je connaisse qui utilise uniquement la bibliothèque standard est

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

Solution rapide:

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

Eh bien, maintenant dans la bibliothèque Scala (au moins en 2.10), il y a quelque chose que vous vouliez – la fonction fusionnée . MAIS il est présenté uniquement dans HashMap pas dans la carte. C’est un peu déroutant. De plus, la signature est encombrante – je ne peux pas imaginer pourquoi j’aurais besoin d’une clé deux fois et quand je devrais produire une paire avec une autre clé. Mais néanmoins, cela fonctionne et beaucoup plus propre que les solutions “natives” précédentes.

 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) }) 

Également dans scaladoc a mentionné que

La méthode merged est en moyenne plus performante que de faire une traversée et de reconstruire une nouvelle carte de hachage immuable ou ++ .

Cela peut être implémenté comme un monoïde avec simplement Scala. Voici un exemple d’implémentation. Avec cette approche, nous pouvons fusionner non seulement 2, mais une liste de cartes.

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

Implémentation basée sur la carte du trait monoïde qui fusionne deux cartes.

 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 } 

Maintenant, si vous avez une liste de cartes à fusionner (dans ce cas, seulement 2), cela peut être fait comme ci-dessous.

 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) 

J’ai écrit un article de blog à ce sujet, regardez-le:

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

essentiellement en utilisant le groupe semi scalaz, vous pouvez réaliser cela assez facilement

ressemblerait à quelque chose comme:

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

La réponse d’Andrzej Doyle contient une excellente explication des semigroupes qui vous permet d’utiliser le |+| opérateur pour joindre deux cartes et additionner les valeurs pour les clés correspondantes.

Il y a de nombreuses façons de définir une instance d’une classe de caractères, et contrairement à l’OP, vous ne souhaitez peut-être pas additionner vos clés de manière spécifique. Ou, vous pourriez vouloir faire fonctionner sur une union plutôt que dans une intersection. Scalaz ajoute également des fonctions supplémentaires à Map à cette fin:

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 peux faire

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

C’est ce que j’ai imaginé …

 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 } 

Vous pouvez aussi le faire avec les chats .

 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) 

J’ai une petite fonction pour faire le travail, c’est dans ma petite bibliothèque pour certaines fonctionnalités fréquemment utilisées qui ne sont pas dans la lib standard. Il devrait fonctionner pour tous les types de cartes, mutables et immuables, pas seulement les HashMaps

Voici l’usage

 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[Ssortingng,Int] = Map(1 -> 2, 2 -> 4) 

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

Et voici le corps

 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

Le moyen le plus rapide et le plus simple:

 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))) ) 

De cette manière, chacun des éléments est immédiatement ajouté à la carte.

Le deuxième moyen ++ est:

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

Contrairement à la première méthode, une nouvelle liste sera créée et concaténée à la carte précédente pour chaque élément d’une deuxième carte.

L’expression de case crée implicitement une nouvelle liste en utilisant unapply méthode non unapply .