Ubah Daftar tuple ke peta (dan tangani kunci duplikat?)

91

Saya sedang memikirkan cara yang bagus untuk mengubah Daftar tupel dengan kunci duplikat [("a","b"),("c","d"),("a","f")]menjadi peta ("a" -> ["b", "f"], "c" -> ["d"]). Biasanya (dalam python), saya akan membuat peta kosong dan for-loop atas daftar dan memeriksa kunci duplikat. Tapi saya mencari sesuatu yang lebih scala-ish dan solusi cerdas di sini.

btw, jenis nilai kunci sebenarnya yang saya gunakan di sini adalah (Int, Node)dan saya ingin mengubahnya menjadi peta(Int -> NodeSeq)

Tg.
sumber

Jawaban:

79

Kelompokkan dan kemudian proyek:

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

Cara yang lebih scalish dengan menggunakan lipatan, dengan cara seperti di sana (lewati map flangkah).

om-nom-nom
sumber
125

Untuk Googler yang tidak mengharapkan duplikat atau tidak keberatan dengan kebijakan penanganan duplikat default :

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

Pada 2.12, kebijakan default berbunyi:

Kunci duplikat akan ditimpa oleh kunci selanjutnya: jika ini adalah koleksi yang tidak berurutan, kunci mana yang ada di peta yang dihasilkan tidak ditentukan.

Cory Klein
sumber
57

Berikut alternatif lain:

x.groupBy(_._1).mapValues(_.map(_._2))
Daniel C. Sobral
sumber
Ini memberi kita Map[String, SeqView[String,Seq[_]]]... apakah ini disengaja?
Luigi Plinge
1
@LuigiPlinge A SeqView[String,Seq[_]]juga a Seq[String]. Masih di belakang saya rasa itu tidak berharga, jadi saya menghapus file view. mapValuesakan tetap melihat nilainya.
Daniel C. Sobral
Ini melakukan pekerjaan dengan sempurna untuk kasus saya (pekerjaan rumah 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 ​​mengembalikan tampilan peta, bukan peta baru scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber
1
Mungkin ingin x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)karena mapValuesekspresi akan dihitung ulang setiap kali digunakan. Lihat issues.scala-lang.org/browse/SI-7005
Jeffrey Aguilera
20

Untuk Karyawan Google yang sangat peduli dengan duplikat:

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
sumber
12

Memulai Scala 2.13, sebagian besar koleksi disediakan dengan metode groupMap yang (seperti namanya) setara (lebih efisien) groupBydiikuti oleh mapValues:

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

Ini:

  • groups elemen berdasarkan bagian pertama dari tupel (bagian grup dari peta grup )

  • mapS mengelompokkan nilai dengan mengambil bagian tupel keduanya (bagian peta dari grup Map )

Ini setara list.groupBy(_._1).mapValues(_.map(_._2))tetapi dilakukan dalam satu lintasan melalui Daftar.

Xavier Guihot
sumber
4

Berikut adalah cara yang lebih idiomatis Scala untuk mengubah daftar tupel menjadi peta yang menangani kunci duplikat. Anda ingin menggunakan lipatan.

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
sumber
1
Menurut Anda, mengapa ini lebih bergaya Scala daripada solusi groupBy-mapValue yang disediakan di sini?
Buat42
@ om-nom-nom pernyataan "Cara yang lebih scalish untuk menggunakan lipatan, dengan cara seperti di sana (lewati peta f langkah)."
cevaris
Saya berharap untuk argumen logis ;-). Baik om-nom-nom maupun artikel terkait tidak memberikan bukti untuk pertanyaan saya. (Atau apakah saya melewatkannya?)
Make42
1
@ Make42 Ini adalah cara yang lebih fp untuk menangani hal ini, karena semua monad adalah monoid, dan menurut hukum monoid dapat dilipat. Di fp, objek dan peristiwa dimodelkan sebagai monad, dan tidak semua monad akan mengimplementasikan groupBy.
soote
4

Di bawah ini Anda dapat menemukan beberapa solusi. (GroupBy, FoldLeft, Agregat, Spark)

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

Variasi GroupBy

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

Variasi Lipat Kiri

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

Variasi Agregat - Mirip dengan lipatan Kiri

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
)

Variasi Spark - Untuk kumpulan data besar (Konversi ke RDD dan ke Peta Biasa dari 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
sumber
2

Anda bisa mencobanya

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
sumber