Converti l'elenco di tuple da mappare (e gestisci la chiave duplicata?)


90

Stavo pensando a un bel modo per convertire un elenco di tuple con chiave duplicata [("a","b"),("c","d"),("a","f")]in mappa ("a" -> ["b", "f"], "c" -> ["d"]). Normalmente (in python), creerei una mappa vuota e un ciclo for sull'elenco e verificherei la chiave duplicata. Ma sto cercando qualcosa di più scala e una soluzione intelligente qui.

btw, il tipo effettivo di valore-chiave che utilizzo qui è (Int, Node)e voglio trasformarlo in una mappa di(Int -> NodeSeq)

Risposte:


78

Raggruppa e poi progetta:

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

Modo più squamoso di usare piega, nel modo in cui (saltare il map fpassaggio).


124

Per i googler che non si aspettano duplicati o che stanno bene con la norma di gestione dei duplicati predefinita :

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

A partire da 2.12, la politica predefinita recita:

Le chiavi duplicate verranno sovrascritte dalle chiavi successive: se si tratta di una raccolta non ordinata, quale chiave si trova nella mappa risultante non è definita.


56

Ecco un'altra alternativa:

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

Questo ci dà un Map[String, SeqView[String,Seq[_]]]... è intenzionale?
Luigi Plinge

1
@LuigiPlinge A SeqView[String,Seq[_]]è anche un Seq[String]. Con il senno di poi non penso che valga la pena, quindi ho rimosso il file view. mapValuesfarà comunque una vista sui valori.
Daniel C. Sobral,

Questo ha funzionato perfettamente per il mio caso (coursera homework): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pair = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pair.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG

mapValues ​​restituisce una vista di una mappa, non una nuova mappa scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber

1
Probabilmente vuole x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)perché l' mapValuesespressione verrà ricalcolata ogni volta che viene utilizzata. Vedi issues.scala-lang.org/browse/SI-7005
Jeffrey Aguilera

20

Per i googler che si preoccupano dei duplicati:

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

Iniziando Scala 2.13, la maggior parte delle raccolte viene fornita con il metodo groupMap che è (come suggerisce il nome) un equivalente (più efficiente) di a groupByseguito da mapValues:

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

Questo:

  • groups elementi basati sulla prima parte delle tuple (parte del gruppo della mappa del gruppo )

  • maps raggruppa i valori prendendo la loro seconda parte di tupla (mappa parte del gruppo Mappa )

Questo è l'equivalente di list.groupBy(_._1).mapValues(_.map(_._2))ma eseguito in un passaggio attraverso l'elenco.


4

Ecco un modo più idiomatico di Scala per convertire un elenco di tuple in una mappa che gestisce chiavi duplicate. Vuoi usare una piega.

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
Perché pensi che questo sia più in stile Scala rispetto alle soluzioni groupBy-mapValue fornite qui?
Make42

@ om-nom-nom dichiarazione "Modo più scalare di usare fold, in un modo simile (saltare il passaggio della mappa f)."
cevaris

Speravo in un argomento logico ;-). Né om-nom-nom né l'articolo collegato hanno fornito prove per la mia domanda. (O mi sono perso?)
Make42

1
@ Make42 È un modo più fp per affrontare questo problema, poiché tutte le monadi sono monoidi e i monoidi per legge sono pieghevoli. In fp, gli oggetti e gli eventi sono modellati come monadi e non tutte le monadi implementeranno groupBy.
fuliggine

4

Di seguito puoi trovare alcune soluzioni. (GroupBy, FoldLeft, Aggregate, Spark)

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

Gruppo per variazione

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

Piega la variazione a sinistra

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

Variazione aggregata - Simile alla piega a sinistra

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
)

Variazione Spark - Per set di big data (conversione in un RDD e in una mappa semplice da 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

Puoi provare questo

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.