Konwertuj listę krotek na mapę (i radzisz sobie ze zduplikowanym kluczem?)

92

Myślałem o fajnym sposobie przekonwertowania listy krotek ze zduplikowanym kluczem [("a","b"),("c","d"),("a","f")]na mapę ("a" -> ["b", "f"], "c" -> ["d"]). Normalnie (w Pythonie) tworzyłem pustą mapę i pętlę for-for na liście i sprawdzałem, czy nie ma zduplikowanego klucza. Ale szukam tutaj czegoś bardziej skalistego i sprytnego.

btw, rzeczywisty typ klucza i wartości, którego tutaj używam, to (Int, Node)i chcę przekształcić go w mapę(Int -> NodeSeq)

Tg.
źródło

Odpowiedzi:

80

Grupuj, a następnie projekt:

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

Bardziej skalowalny sposób użycia fold, tak jak tam (pomiń map fkrok).

mniam mniam mniam
źródło
125

Dla pracowników Google, którzy nie oczekują duplikatów lub nie mają nic przeciwko domyślnym zasadom postępowania z duplikatami :

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

Od 2.12 domyślna zasada brzmi:

Zduplikowane klucze zostaną nadpisane późniejszymi kluczami: jeśli jest to kolekcja nieuporządkowana, klucz w wynikowej mapie jest niezdefiniowany.

Cory Klein
źródło
58

Oto inna alternatywa:

x.groupBy(_._1).mapValues(_.map(_._2))
Daniel C. Sobral
źródło
To daje nam Map[String, SeqView[String,Seq[_]]]... czy jest to zamierzone?
Luigi Plinge
1
@LuigiPlinge A SeqView[String,Seq[_]]jest również Seq[String]. Z perspektywy czasu uważam, że to nie jest warte zachodu, więc usunąłem view. mapValuesi tak zrobi przegląd wartości.
Daniel C. Sobral
To zadziałało idealnie w moim przypadku (praca domowa coursera): lazy val DictionaryByOccurrences: Map [Occurrences, List [Word]] = {val pairs = for (curWord <- dictionary) yield {val curWordOccurrences = wordOccurrences (curWord) (curWordOccurrences, curWord)} pairs.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG
mapValues ​​zwraca widok mapy, a nie nową mapę scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber
1
Prawdopodobnie chcesz, x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)ponieważ mapValueswyrażenie zostanie ponownie obliczone za każdym razem, gdy zostanie użyte. Zobacz issue.scala-lang.org/browse/SI-7005
Jeffrey Aguilera,
20

Dla pracowników Google, którym zależy na duplikatach:

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
źródło
12

Zaczynając Scala 2.13, większość kolekcji jest wyposażona w metodę groupMap , która jest (jak sama nazwa wskazuje) odpowiednikiem (bardziej wydajnym) a, groupBypo którym następuje mapValues:

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

To:

  • groupelementy s oparte na pierwszej części krotek (część grupowa mapy grupowej )

  • maps zgrupowane wartości, biorąc ich drugą część krotki (część mapy grupy mapy )

Jest to odpowiednik, list.groupBy(_._1).mapValues(_.map(_._2))ale wykonywany w jednym przejściu przez listę.

Xavier Guihot
źródło
4

Oto bardziej idiomatyczny sposób Scala na przekonwertowanie listy krotek na mapę obsługującą zduplikowane klucze. Chcesz użyć fałdy.

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
źródło
1
Jak myślisz, dlaczego jest to bardziej w stylu Scala niż przedstawione tutaj rozwiązania groupBy-mapValue?
Make42
@ om-nom-nom, instrukcja "Bardziej skalowalny sposób użycia fold, tak jak tam (pomiń mapę f krok)."
cevaris
Liczyłem na logiczny argument ;-). Ani om-nom-nom, ani powiązany artykuł nie dostarczyły dowodów na moje pytanie. (A może przegapiłem to?)
Make42
1
@ Make42 Jest to bardziej fp sposób radzenia sobie z tym problemem, ponieważ wszystkie monady są monoidami, a monoidy zgodnie z prawem są składane. W fp obiekty i zdarzenia są modelowane jako monady i nie wszystkie monady będą implementować groupBy.
soote
4

Poniżej znajdziesz kilka rozwiązań. (GroupBy, FoldLeft, Aggregate, Spark)

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

Grupa według odmian

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

Odmiana zagięcia w lewo

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

Zagregowana odmiana - podobnie jak spasowanie w lewo

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 - dla dużych zbiorów danych (konwersja na RDD i zwykłą mapę z 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
źródło
2

Możesz tego spróbować

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
źródło