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.