Convertir lista de tupla en mapa (¿y tratar con clave duplicada?)

90

Estaba pensando en una buena manera de convertir una lista de tuplas con clave duplicada [("a","b"),("c","d"),("a","f")]en un mapa ("a" -> ["b", "f"], "c" -> ["d"]). Normalmente (en Python), crearía un mapa vacío y for-loop sobre la lista y buscaría una clave duplicada. Pero estoy buscando una solución más inteligente y escalable aquí.

por cierto, el tipo real de clave-valor que uso aquí es (Int, Node)y quiero convertir en un mapa de(Int -> NodeSeq)

Tg.
fuente

Respuestas:

78

Agrupar y luego proyectar:

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

Una forma más escalofriante de usar el pliegue, de la misma manera que allí (omitir el map fpaso).

Om nom nom
fuente
124

Para los empleados de Google que no esperan duplicados o están de acuerdo con la política de manejo de duplicados predeterminada :

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

A partir de la 2.12, la política predeterminada dice:

Las claves duplicadas se sobrescribirán con claves posteriores: si se trata de una colección desordenada, qué clave está en el mapa resultante no está definida.

Cory Klein
fuente
56

Aquí tienes otra alternativa:

x.groupBy(_._1).mapValues(_.map(_._2))
Daniel C. Sobral
fuente
Esto nos da un Map[String, SeqView[String,Seq[_]]]... ¿es intencional?
Luigi Plinge
1
@LuigiPlinge A SeqView[String,Seq[_]]también es un Seq[String]. Aún en retrospectiva, no creo que valga la pena, así que eliminé el view. mapValueshará una vista de todos modos en los valores.
Daniel C. Sobral
Esto funcionó perfectamente para mi caso (tarea de coursera): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pairs = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pares.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG
mapValues ​​devuelve una vista de un mapa, no un mapa nuevo scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber
1
Probablemente quiera x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)porque la mapValuesexpresión se volverá a calcular cada vez que se utilice. Ver issues.scala-lang.org/browse/SI-7005
Jeffrey Aguilera
20

Para los empleados de Google que se preocupan por los duplicados:

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")) 
pathikrit
fuente
12

Comenzando Scala 2.13, la mayoría de las colecciones se proporcionan con el método groupMap que es (como su nombre sugiere) un equivalente (más eficiente) de groupByseguido de mapValues:

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

Esta:

  • groups elementos basados ​​en la primera parte de tuplas (parte de grupo del mapa de grupo )

  • maps valores agrupados tomando su segunda parte de tupla (parte del mapa del grupo Mapa )

Esto es equivalente a, list.groupBy(_._1).mapValues(_.map(_._2))pero se realiza en una sola pasada por la Lista.

Xavier Guihot
fuente
4

Aquí hay una forma más idiomática de Scala para convertir una lista de tuplas en un mapa que maneja claves duplicadas. Quieres usar un pliegue.

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))
cevaris
fuente
1
¿Por qué cree que esto es más al estilo Scala que las soluciones groupBy-mapValue que se proporcionan aquí?
Make42
Declaración de @ om-nom-nom "Una forma más escalofriante de usar el pliegue, de la misma manera que allí (omitir el paso del mapa f)".
cevaris
Esperaba un argumento lógico ;-). Ni om-nom-nom ni el artículo vinculado proporcionaron evidencia para mi pregunta. (¿O me lo
perdí
1
@ Make42 Es una forma más fp de lidiar con esto, ya que todas las mónadas son monoides y, por ley, los monoides son plegables. En fp, los objetos y eventos se modelan como mónadas, y no todas las mónadas implementarán groupBy.
soote
4

A continuación puede encontrar algunas soluciones. (GroupBy, FoldLeft, Agregado, Spark)

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

Grupo por variación

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

Doble variación a la izquierda

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

Variación agregada: similar al pliegue a la izquierda

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
)

Spark Variation: para grandes conjuntos de datos (conversión a un RDD y a un mapa simple 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
Melcom van Eeden
fuente
2

Puedes probar esto

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