Converter lista de tupla em mapa (e lidar com chave duplicada?)

90

Eu estava pensando em uma boa maneira de converter uma lista de tupla com chave duplicada [("a","b"),("c","d"),("a","f")]em um mapa ("a" -> ["b", "f"], "c" -> ["d"]). Normalmente (em python), eu criaria um mapa vazio e faria um loop for sobre a lista e verificaria se há chaves duplicadas. Mas estou procurando uma solução mais escalonada e inteligente aqui.

btw, o tipo real de valor-chave que uso aqui é (Int, Node)e quero transformar em um mapa de(Int -> NodeSeq)

Tg.
fonte

Respostas:

78

Grupo e depois projeto:

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

Maneira mais scalish usar vezes, na forma como existe (pular map fpasso).

om-nom-nom
fonte
124

Para Googlers que não esperam duplicatas ou aceitam a política padrão de tratamento de duplicatas :

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

A partir de 2.12, a política padrão diz:

Chaves duplicadas serão sobrescritas por chaves posteriores: se esta for uma coleção não ordenada, qual chave está no mapa resultante é indefinida.

Cory Klein
fonte
56

Aqui está outra alternativa:

x.groupBy(_._1).mapValues(_.map(_._2))
Daniel C. Sobral
fonte
Isso nos dá um Map[String, SeqView[String,Seq[_]]]... isso é intencional?
Luigi Plinge
1
@LuigiPlinge A SeqView[String,Seq[_]]também é um Seq[String]. Ainda em retrospectiva, não acho que valha a pena, então removi o view. mapValuesfará uma visão de qualquer maneira sobre os valores.
Daniel C. Sobral
Isso funcionou perfeitamente para o meu caso (lição de casa do coursera): lazy val dictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pair = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pairs.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG
mapValues ​​retorna uma visualização de um mapa, não um novo mapa scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber
1
Provavelmente deseja x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)porque a mapValuesexpressão será recomputada cada vez que for usada. Veja issues.scala-lang.org/browse/SI-7005
Jeffrey Aguilera
20

Para Googlers que se preocupam com duplicatas:

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")) 
patikrit
fonte
12

A partir Scala 2.13, a maioria das colecções são fornecidos com o groupMap método é que (como o nome sugere) um equivalente (mais eficaz) de um groupByseguido por mapValues:

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

Este:

  • groups elementos baseados na primeira parte das tuplas (grupo parte do grupo Mapa)

  • maps valores agrupados tomando a sua segunda parte tuplo (mapear parte do grupo Mapa )

Isso é equivalente, list.groupBy(_._1).mapValues(_.map(_._2))mas executado em uma passagem pela Lista.

Xavier Guihot
fonte
4

Esta é uma maneira mais idiomática do Scala de converter uma lista de tuplas em um mapa que manipula chaves duplicadas. Você deseja usar uma dobra.

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
fonte
1
Por que você acha que isso é mais no estilo Scala do que as soluções groupBy-mapValue fornecidas aqui?
Make42 de
Declaração de @ om-nom-nom "Maneira mais escalonada de usar a dobra, da forma como esta (pular etapa f do mapa)."
cevaris
Eu esperava um argumento lógico ;-). Nem om-nom-nom nem o artigo vinculado forneceram evidências para minha pergunta. (Ou eu perdi?)
42 de
1
@ Make42 É uma maneira mais fp de lidar com isso, já que todas as mônadas são monóides e, por lei, os monóides são dobráveis. No fp, os objetos e eventos são modelados como mônadas, e nem todas as mônadas implementarão groupBy.
soote
4

Abaixo você pode encontrar algumas soluções. (GroupBy, FoldLeft, Aggregate, Spark)

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

Variação GroupBy

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

Variação dobra para a esquerda

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

Variação agregada - semelhante à dobra esquerda

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
)

Variação do Spark - Para conjuntos de dados grandes (conversão para um RDD e para um mapa simples 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
fonte
2

Você pode tentar isso

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
fonte