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)

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


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.


56

Aquí tienes otra alternativa:

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

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

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.


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

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

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.