Scala: fusionner des cartes par clé

Disons que j'ai deux cartes:

val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

Je veux fusionner ces cartes par clé, en appliquant une fonction pour collecter les valeurs (dans ce cas particulier, je veux les collecter dans un seq, en donnant:

val c = Map(1 -> Seq("one", "un"), 2->Seq("two", "deux"), 3->Seq("three", "trois"))

On dirait qu'il devrait y avoir une belle façon idiomatique de le faire-des suggestions? Je suis heureux si la solution implique scalaz.

22
demandé sur Submonoid 2011-10-13 17:55:43

6 réponses

scala.collection.immutable.IntMap a une méthode intersectionWith qui fait exactement ce que vous voulez (je crois):

import scala.collection.immutable.IntMap

val a = IntMap(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "four")
val b = IntMap(1 -> "un", 2 -> "deux", 3 -> "trois")

val merged = a.intersectionWith(b, (_, av, bv: String) => Seq(av, bv))

Cela vous donne IntMap(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois)). Notez qu'il ignore correctement la clé qui se produit uniquement dans a.

Comme note de côté: je me suis souvent retrouvé à vouloir le unionWith, intersectionWith, etc. fonctions de Haskell Data.Map à Scala. Je ne pense pas qu'il y ait une raison de principe qu'ils ne devraient être disponibles que sur IntMap, au lieu de dans le trait de base collection.Map.

19
répondu Travis Brown 2011-10-13 18:03:31
val a = Map(1 -> "one", 2 -> "two", 3 -> "three")
val b = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

val c = a.toList ++ b.toList
val d = c.groupBy(_._1).map{case(k, v) => k -> v.map(_._2).toSeq}
//res0: scala.collection.immutable.Map[Int,Seq[java.lang.String]] =
        //Map((2,List(two, deux)), (1,List(one, un), (3,List(three, trois)))
18
répondu Infinity 2011-10-13 14:10:00

Scalaz ajoute une méthode |+| pour tout type A pour lequel un Semigroup[A] est disponible.

Si vous avez mappé vos cartes de sorte que chaque valeur soit une séquence à un seul élément, vous pouvez l'utiliser tout simplement:

scala> a.mapValues(Seq(_)) |+| b.mapValues(Seq(_))
res3: scala.collection.immutable.Map[Int,Seq[java.lang.String]] = Map(1 -> List(one, un), 2 -> List(two, deux), 3 -> List(three, trois))
14
répondu Ben James 2011-10-13 14:11:30

Donc je n'étais pas très content de l'une ou l'autre solution (je veux construire un nouveau type, donc semigroup ne se sent pas vraiment approprié, et la solution D'Infinity semblait assez complexe), donc je suis allé avec ceci pour le moment. Je serais heureux de le voir amélioré:

def merge[A,B,C](a : Map[A,B], b : Map[A,B])(c : (B,B) => C) = {
  for (
    key <- (a.keySet ++ b.keySet);
    aval <- a.get(key); bval <- b.get(key)
  ) yield c(aval, bval)
}
merge(a,b){Seq(_,_)}

Je voulais le comportement de ne rien renvoyer quand une clé n'était pas présente dans l'une ou l'autre carte (ce qui diffère des autres solutions), mais une façon de spécifier cela serait bien.

2
répondu Submonoid 2011-10-13 14:40:43

Voici ma première approche avant de chercher les autres solutions:

for (x <- a) yield 
  x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten

Pour éviter les éléments qui n'existent que dans a ou b, un filtre est pratique:

(for (x <- a) yield 
  x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2)

Aplatir est nécessaire, car B. get (X. _1) renvoie une Option. Pour faire aplatir le travail, le premier élément doit aussi être une option, donc nous ne pouvons pas simplement utiliser X. _2 ici.

Pour les séquences, cela fonctionne aussi:

scala> val b = Map (1 -> Seq(1, 11, 111), 2 -> Seq(2, 22), 3 -> Seq(33, 333), 5 -> Seq(55, 5, 5555))
b: scala.collection.immutable.Map[Int,Seq[Int]] = Map(1 -> List(1, 11, 111), 2 -> List(2, 22), 3 -> List(33, 333), 5 -> List(55, 5, 5555))

scala> val a = Map (1 -> Seq(1, 101), 2 -> Seq(2, 212, 222), 3 -> Seq (3, 3443), 4 -> (44, 4, 41214))
a: scala.collection.immutable.Map[Int,ScalaObject with Equals] = Map(1 -> List(1, 101), 2 -> List(2, 212, 222), 3 -> List(3, 3443), 4 -> (44,4,41214))

scala> (for (x <- a) yield x._1 -> Seq (a.get (x._1), b.get (x._1)).flatten).filter (_._2.size == 2) 
res85: scala.collection.immutable.Map[Int,Seq[ScalaObject with Equals]] = Map(1 -> List(List(1, 101), List(1, 11, 111)), 2 -> List(List(2, 212, 222), List(2, 22)), 3 -> List(List(3, 3443), List(33, 333)))
1
répondu user unknown 2011-10-14 10:19:38
val fr = Map(1 -> "one", 2 -> "two", 3 -> "three")
val en = Map(1 -> "un", 2 -> "deux", 3 -> "trois")

def innerJoin[K, A, B](m1: Map[K, A], m2: Map[K, B]): Map[K, (A, B)] = {
  m1.flatMap{ case (k, a) => 
    m2.get(k).map(b => Map((k, (a, b)))).getOrElse(Map.empty[K, (A, B)])
  }
}

innerJoin(fr, en) // Map(1 -> ("one", "un"), 2 -> ("two", "deux"), 3 -> ("three", "trois")): Map[Int, (String, String)]
1
répondu Guillaume Massé 2016-06-01 18:20:01