Comment puis-je fusionner des cartes comme ci-dessous:
Map1 = Map(1 -> Class1(1), 2 -> Class1(2))
Map2 = Map(2 -> Class2(1), 3 -> Class2(2))
Après avoir fusionné.
Merged = Map( 1 -> List(Class1(1)), 2 -> List(Class1(2), Class2(1)), 3 -> Class2(2))
Peut être List, Set ou toute autre collection ayant un attribut size.
En utilisant la bibliothèque standard, vous pouvez le faire comme suit:
// convert maps to seq, to keep duplicate keys and concat
val merged = Map(1 -> 2).toSeq ++ Map(1 -> 4).toSeq
// merged: Seq[(Int, Int)] = ArrayBuffer((1,2), (1,4))
// group by key
val grouped = merged.groupBy(_._1)
// grouped: scala.collection.immutable.Map[Int,Seq[(Int, Int)]] = Map(1 -> ArrayBuffer((1,2), (1,4)))
// remove key from value set and convert to list
val cleaned = grouped.mapValues(_.map(_._2).toList)
// cleaned: scala.collection.immutable.Map[Int,List[Int]] = Map(1 -> List(2, 4))
Ceci est la mise en œuvre la plus simple que j'ai pu trouver,
val m1 = Map(1 -> "1", 2 -> "2")
val m2 = Map(2 -> "21", 3 -> "3")
def merge[K, V](m1:Map[K, V], m2:Map[K, V]):Map[K, List[V]] =
(m1.keySet ++ m2.keySet) map { i => i -> (m1.get(i).toList ::: m2.get(i).toList) } toMap
merge(m1, m2) // Map(1 -> List(1), 2 -> List(2, 21), 3 -> List(3))
Vous pouvez utiliser scalaz :
import scalaz._, Scalaz._
val m1 = Map('a -> 1, 'b -> 2)
val m2 = Map('b -> 3, 'c -> 4)
m1.mapValues{List(_)} |+| m2.mapValues{List(_)}
// Map('b -> List(2, 3), 'c -> List(4), 'a -> List(1))
Vous pouvez utiliser Set(_)
au lieu de List(_)
pour obtenir Set
s comme valeurs dans Map
.
Voir Semigroup dans Aide-mémoire scalaz (ou dans apprentissage scalaz ) pour plus de détails sur |+|
.
Pour Int
|+|
Fonctionne comme +
, Pour List
- comme ++
, Pour Map
cela s'applique |+|
Aux valeurs des mêmes clés.
J'ai écrit un blog à ce sujet, consultez-le:
http://www.nimrodstech.com/scala-map-merge/
en utilisant essentiellement le groupe semi-scalaz, vous pouvez y parvenir assez facilement
ressemblerait à quelque chose comme:
import scalaz.Scalaz._
Map1 |+| Map2
Une façon propre de le faire, avec chats :
import cats.implicits._
Map(1 -> "Hello").combine(Map(2 -> "Goodbye"))
//Map(2 -> Goodbye, 1 -> Hello)
Il est important de noter que les deux cartes doivent être du même type (dans ce cas, Map[Int, String]
).
Explication longue:
combine
n'est pas vraiment membre de Map. En important cats.implicits, vous mettez dans le champ d'application les instances monoïdes intégrées de cats, ainsi que certaines classes implicites qui permettent la syntaxe laconique.
Ce qui précède est équivalent à ceci:
Monoid[Map[Int, String]].combine(Map(1 -> "Hello"), Map(2 -> "Goodbye"))
Où nous utilisons la fonction fonction "invocateur" monoïde pour obtenir l'instance Monoid [Map [Int, String]] dans la portée et en utilisant sa fonction de combinaison.
Vous pouvez utiliser foldLeft pour fusionner deux cartes du même type
def merge[A, B](a: Map[A, B], b: Map[A, B])(mergef: (B, Option[B]) => B): Map[A, B] = {
val (big, small) = if (a.size > b.size) (a, b) else (b, a)
small.foldLeft(big) { case (z, (k, v)) => z + (k -> mergef(v, z.get(k))) }
}
def mergeIntSum[A](a: Map[A, Int], b: Map[A, Int]): Map[A, Int] =
merge(a, b)((v1, v2) => v2.map(_ + v1).getOrElse(v1))
Exemple:
val a = Map("a" -> 1, "b" -> 5, "c" -> 6)
val b = Map("a" -> 4, "z" -> 8)
mergeIntSum(a, b)
res0: Map[String,Int] = Map(a -> 5, b -> 5, c -> 6, z -> 8)
A partir de Scala 2.13
, Une autre solution uniquement basée sur la bibliothèque standard consiste à utiliser groupMap
qui (comme son nom l'indique) est l'équivalent d'un groupBy
suivi de mapValues
:
// val m1 = Map(1 -> "a", 2 -> "b")
// val m2 = Map(2 -> "c", 3 -> "d")
(m1.toSeq ++ m2).groupMap(_._1)(_._2)
// Map[Int,Seq[String]] = Map(2 -> List("b", "c"), 1 -> List("a"), 3 -> List("d"))
Cette:
Concatène les deux cartes en tant que séquence de tuples (List((1,"a"), (2,"b"), (2,"c"), (3,"d"))
). Par souci de concision, m2
Est implicitement converti en Seq
pour s'adapter au type de m1.toSeq
- mais vous pouvez choisir de le rendre explicite en en utilisant m2.toSeq
.
group
s éléments basés sur leur première partie Tuple (_._1
) (partie de groupe du groupe Carte)
map
s ont groupé les valeurs dans leur deuxième partie Tuple (_._2
) (partie de la carte du groupe Carte )
Solution pour combiner deux cartes: Map[A,B]
, le type de résultat: Map[A,List[B]]
via le Scala Cats (version légèrement améliorée, offerte par @David Castillo)
// convertit chaque carte d'origine en Carte [A, Liste [B]]. // Ajoutez une instance de Monoid [List] dans la portée pour combiner les listes:
import cats.instances.map._ // for Monoid
import cats.syntax.semigroup._ // for |+|
import cats.instances.list._
val map1 = Map("a" -> 1, "b" -> 2)
.mapValues(List(_))
val map2 = Map("b" -> 3, "d" -> 4)
.mapValues(List(_))
map1 |+| map2
Si vous ne voulez pas jouer avec les cartes originales, vous pouvez faire quelque chose comme
val target = map1.clone()
val source = map2.clone()
source.foreach(e => target += e._1 -> e._2)
left.keys map { k => k -> List(left(k),right(k)) } toMap
Est concis et fonctionnera, en supposant que vos deux cartes sont left
et right
. Pas sûr de l'efficacité.
Mais votre question est un peu ambiguë, pour deux raisons. Vous ne spécifiez pas
class1
, class2
),Pour le premier cas, considérons l'exemple suivant:
val left = Map("foo" ->1, "bar" ->2)
val right = Map("bar" -> 'a', "foo" -> 'b')
Ce qui se traduit par
res0: Map[String,List[Int]] = Map(foo -> List(1, 98), bar -> List(2, 97))
Remarquez comment les Char
s ont été convertis en Int
s, en raison de la hiérarchie de type scala. Plus généralement, si dans votre exemple class1
et class2
ne sont pas liés, vous obtiendrez un List[Any]
; ce n'est probablement pas ce que vous vouliez.
Vous pouvez contourner ce problème en supprimant le constructeur List
de ma réponse; cela renverra Tuple
s qui préservent le type:
res0: Map[String,(Int, Char)] = Map(foo -> (1,b), bar -> (2,a))
Le deuxième problème est ce qui se passe lorsque vous avez des cartes qui n'ont pas les mêmes clés. Cela se traduira par un key not found
exception. Autrement dit, faites-vous une jointure gauche, droite ou intérieure des deux cartes? Vous pouvez lever l'ambiguïté du type de jointure en passant à right.keys
ou right.keySet ++ left.keySet
pour les jointures droite/intérieure respectivement. Ce dernier contournera le problème de clé manquante, mais ce n'est peut-être pas ce que vous voulez, c'est-à-dire que vous souhaitez plutôt une jointure gauche ou droite. Dans ce cas, vous pouvez envisager d'utiliser la méthode withDefault
de Map
pour vous assurer que chaque clé renvoie une valeur, par exemple None
, mais cela nécessite un peu plus de travail.
m2.foldLeft(m1.mapValues{List[CommonType](_)}) { case (acc, (k, v)) =>
acc.updated(k, acc.getOrElse(k, List.empty) :+ v)
}
Comme indiqué par jwvh, le type de liste doit être spécifié explicitement si Class1 n'est pas un type supérieur lié à Class2. CommonType est un type qui est à la fois supérieur pour Class1 et Class2.
Il existe un module Scala appelé scala-collection-contrib , qui propose des méthodes très utiles comme mergeByKey
.
Tout d'abord, nous devons ajouter une dépendance supplémentaire à build.sbt
:
libraryDependencies += "org.scala-lang.modules" %% "scala-collection-contrib" % "0.1.0"
puis il est possible de faire une fusion comme ceci:
import scala.collection.decorators._
val map1 = Map(1 -> Class1(1), 2 -> Class1(2))
val map2 = Map(2 -> Class2(1), 3 -> Class2(2))
map1.mergeByKeyWith(map2){
case (a,b) => a.toList ++ b.toList
}