Convertir la liste de tuple en mappage (et gérer la clé en double?)


90

Je réfléchissais à un bon moyen de convertir une liste de tuple avec une clé en double [("a","b"),("c","d"),("a","f")] en en carte ("a" -> ["b", "f"], "c" -> ["d"]). Normalement (en python), je créerais une carte vide et une boucle for sur la liste et vérifierais la clé en double. Mais je recherche quelque chose de plus scala-ish et de solution intelligente ici.

btw, le type réel de valeur-clé que j'utilise ici est (Int, Node)et je veux devenir une carte de(Int -> NodeSeq)

Réponses:


78

Grouper puis projeter:

scala> val x = List("a" -> "b", "c" -> "d", "a" -> "f")
//x: List[(java.lang.String, java.lang.String)] = List((a,b), (c,d), (a,f))
scala> x.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
//res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(c -> List(d), a -> List(b, f))

Manière plus évolutive d'utiliser le pli, comme ici (sautez l' map fétape).


124

Pour les Googleurs qui ne s'attendent pas à des doublons ou qui acceptent la politique de gestion des doublons par défaut :

List("a" -> 1, "b" -> 2).toMap
// Result: Map(a -> 1, c -> 2)

À partir de 2.12, la politique par défaut se lit comme suit:

Les clés en double seront écrasées par les clés ultérieures: s'il s'agit d'une collection non ordonnée, quelle clé se trouve dans la carte résultante n'est pas définie.


56

Voici une autre alternative:

x.groupBy(_._1).mapValues(_.map(_._2))

Cela nous donne un Map[String, SeqView[String,Seq[_]]]... est-ce intentionnel?
Luigi Plinge

1
@LuigiPlinge A SeqView[String,Seq[_]]est également un Seq[String]. Toujours avec le recul, je ne pense pas que cela en vaille la peine, alors j'ai supprimé le fichier view. mapValuesfera quand même une vue sur les valeurs.
Daniel C. Sobral

Cela a parfaitement fonctionné pour mon cas (coursera devoirs): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pairs = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pairs.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG

mapValues ​​renvoie une vue d'une carte, pas une nouvelle carte scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber

1
Probablement souhaité x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)car l' mapValuesexpression sera recalculée à chaque fois qu'elle sera utilisée. Voir issues.scala-lang.org/browse/SI-7005
Jeffrey Aguilera

20

Pour les Googleurs qui se soucient des doublons:

implicit class Pairs[A, B](p: List[(A, B)]) {
  def toMultiMap: Map[A, List[B]] = p.groupBy(_._1).mapValues(_.map(_._2))
}

> List("a" -> "b", "a" -> "c", "d" -> "e").toMultiMap
> Map("a" -> List("b", "c"), "d" -> List("e")) 

12

Au départ Scala 2.13, la plupart des collections sont fournies avec la méthode groupMap qui est (comme son nom l'indique) un équivalent (plus efficace) d'un groupBysuivi de mapValues:

List("a" -> "b", "c" -> "d", "a" -> "f").groupMap(_._1)(_._2)
// Map[String,List[String]] = Map(a -> List(b, f), c -> List(d))

Ce:

  • groups éléments basés sur la première partie des tuples (partie de groupe de la carte de groupe )

  • maps valeurs groupées en prenant leur deuxième partie de tuple (partie map du groupe Map )

Ceci est un équivalent de list.groupBy(_._1).mapValues(_.map(_._2))mais effectué en un seul passage dans la liste.


4

Voici une manière plus idiomatique Scala de convertir une liste de tuples en une carte gérant les clés en double. Vous souhaitez utiliser un pli.

val x = List("a" -> "b", "c" -> "d", "a" -> "f")

x.foldLeft(Map.empty[String, Seq[String]]) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, Seq.empty[String]) ++ Seq(v))
}

res0: scala.collection.immutable.Map[String,Seq[String]] = Map(a -> List(b, f), c -> List(d))

1
Pourquoi pensez-vous que c'est plus de style Scala que les solutions groupBy-mapValue fournies ici?
Make42

@ om-nom-nom statement "Une manière plus scalish d'utiliser fold, comme ici (sautez l'étape de la carte f)."
cevaris

J'espérais un argument logique ;-). Ni om-nom-nom ni l'article lié n'ont fourni de preuves pour ma question. (Ou l'ai-je manqué?)
Make42

1
@ Make42 C'est une façon plus fp de gérer cela, puisque toutes les monades sont des monoïdes et que les monoïdes de par la loi sont pliables. Dans fp, les objets et les événements sont modélisés comme des monades, et toutes les monades n'implémenteront pas groupBy.
suote le

4

Vous trouverez ci-dessous quelques solutions. (GroupBy, FoldLeft, Aggregate, Spark)

val list: List[(String, String)] = List(("a","b"),("c","d"),("a","f"))

Variation GroupBy

list.groupBy(_._1).map(v => (v._1, v._2.map(_._2)))

Plier la variante gauche

list.foldLeft[Map[String, List[String]]](Map())((acc, value) => {
  acc.get(value._1).fold(acc ++ Map(value._1 -> List(value._2))){ v =>
    acc ++ Map(value._1 -> (value._2 :: v))
  }
})

Variation agrégée - Similaire au pli à gauche

list.aggregate[Map[String, List[String]]](Map())(
  (acc, value) => acc.get(value._1).fold(acc ++ Map(value._1 -> 
    List(value._2))){ v =>
     acc ++ Map(value._1 -> (value._2 :: v))
  },
  (l, r) => l ++ r
)

Variation Spark - Pour les ensembles de données volumineux (conversion en RDD et en carte simple à partir de RDD)

import org.apache.spark.rdd._
import org.apache.spark.{SparkContext, SparkConf}

val conf: SparkConf = new 
SparkConf().setAppName("Spark").setMaster("local")
val sc: SparkContext = new SparkContext (conf)

// This gives you a rdd of the same result
val rdd: RDD[(String, List[String])] = sc.parallelize(list).combineByKey(
   (value: String) => List(value),
   (acc: List[String], value) => value :: acc,
   (accLeft: List[String], accRight: List[String]) => accLeft ::: accRight
)

// To convert this RDD back to a Map[(String, List[String])] you can do the following
rdd.collect().toMap

2

Vous pouvez essayer ceci

scala> val b = new Array[Int](3)
// b: Array[Int] = Array(0, 0, 0)
scala> val c = b.map(x => (x -> x * 2))
// c: Array[(Int, Int)] = Array((1,2), (2,4), (3,6))
scala> val d = Map(c : _*)
// d: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 4, 3 -> 6)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.