Scala cara terbaik untuk mengubah Koleksi menjadi kunci-peta?

165

Jika saya memiliki koleksi ctipe Tdan ada properti pdi T(tipe P, katakanlah), apa cara terbaik untuk melakukan kunci peta-dengan-mengekstraksi ?

val c: Collection[T]
val m: Map[P, T]

Salah satu caranya adalah sebagai berikut:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

Tapi sekarang aku butuh peta yang bisa berubah . Apakah ada cara yang lebih baik untuk melakukan ini sehingga itu dalam 1 baris dan saya berakhir dengan Peta abadi ? (Jelas saya bisa mengubah hal di atas menjadi utilitas perpustakaan sederhana, seperti yang saya lakukan di Jawa, tetapi saya menduga bahwa di Scala tidak perlu)

oxbow_lakes
sumber

Jawaban:

232

Kamu bisa memakai

c map (t => t.getP -> t) toMap

tapi ketahuilah bahwa ini membutuhkan 2 traversal.

Ben Lings
sumber
8
Saya masih lebih suka saran saya di trac a Traversable[K].mapTo( K => V)dan Traversable[V].mapBy( V => K)lebih baik!
oxbow_lakes
7
Sadarilah bahwa ini adalah operasi kuadratik, tetapi hal yang sama berlaku untuk sebagian besar varian lain yang diberikan di sini. Melihat kode sumber scala.collection.mutable.MapBuilder dll, menurut saya bahwa untuk setiap tuple, peta baru yang tidak berubah dibuat untuk tupel yang ditambahkan.
jcsahnwaldt Reinstate Monica
30
Pada mesin saya untuk daftar dengan 500.000 elemen, kode Scala ini sekitar 20 kali lebih lambat daripada pendekatan Java yang lurus ke depan (buat HashMap dengan ukuran yang sesuai, daftar loop over, masukkan elemen ke dalam peta). Untuk 5.000 elemen, Scala ist sekitar 8 kali lebih lambat. Pendekatan loop yang ditulis dalam Scala kira-kira 3 kali lebih cepat dari varian toMap, tetapi masih antara 2 dan 7 kali lebih lambat dari Java.
jcsahnwaldt Reinstate Monica
8
Apakah Anda mau memberikan sumber tes ke komunitas SO? Terima kasih.
user573215
8
Ganti cdengan c.iteratoruntuk menghindari pembuatan koleksi perantara.
ghik
21

Anda dapat membuat Peta dengan jumlah variabel tupel. Jadi gunakan metode peta pada koleksi untuk mengubahnya menjadi kumpulan tupel dan kemudian gunakan: _ * trik untuk mengubah hasilnya menjadi argumen variabel.

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)
James Iry
sumber
5
Saya telah membaca tentang Scala selama> 2 minggu dan mengerjakan contoh-contoh dan belum pernah melihat notasi ": _ *" ini! Terima kasih banyak atas bantuan Anda
oxbow_lakes
Sebagai catatan, saya bertanya-tanya mengapa kita perlu tepat bahwa ini adalah urutan dengan _ . peta masih mengkonversi mengembalikan daftar tuple di sini. Jadi mengapa _ ? Maksud saya ini berfungsi tetapi saya ingin memahami jenis anggapan di sini
MaatDeamon
1
Apakah ini lebih efisien daripada metode lain?
Jus12
16

Selain solusi @James Iry, Anda juga dapat melakukannya menggunakan lipatan. Saya menduga bahwa solusi ini sedikit lebih cepat daripada metode tuple (lebih sedikit objek sampah yang dibuat):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Daniel Spiewak
sumber
Saya akan mencoba ini (saya yakin itu berhasil :-). Apa yang terjadi dengan fungsi "(m, s) => m (s) = s.length"? Saya telah melihat contoh foldLeft pada umumnya dengan jumlah dan fungsi "_ + _"; ini jauh lebih membingungkan! Fungsi tampaknya menganggap bahwa saya sudah memiliki tuple (m, s), yang saya tidak benar-benar dapatkan
oxbow_lakes
2
Man, Scala aneh saat itu!
missingfaktor
8
@Daniel Saya mencoba kode Anda, tetapi muncul kesalahan berikut: "pembaruan nilai bukan anggota scala.collection.immutable.Map [String, Int]". Tolong jelaskan kode Anda cara mengerjakan kode ini?
mr.boyfox
1
sepertinya tidak berfungsi. Bagi saya "Aplikasi tidak mengambil parameter"
jayunit100
7
Versi berubah: list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Catatan bahwa jika Anda ingin menggunakan koma untuk membangun tupel, Anda perlu tambahan sepasang kurung: ((s, s.length)).
Kelvin
11

Ini dapat diimplementasikan secara kekal dan dengan satu traversal dengan melipat melalui koleksi sebagai berikut.

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

Solusinya bekerja karena menambahkan ke Peta yang tidak dapat diubah mengembalikan Peta yang tidak bisa diubah baru dengan entri tambahan dan nilai ini berfungsi sebagai akumulator melalui operasi lipat.

Yang menarik di sini adalah kesederhanaan kode versus efisiensinya. Jadi, untuk koleksi besar, pendekatan ini mungkin lebih cocok daripada menggunakan 2 implementasi traversal seperti menerapkan mapdan toMap.

RamV13
sumber
8

Solusi lain (mungkin tidak bekerja untuk semua jenis)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

ini menghindari pembuatan daftar perantara, info lebih lanjut di sini: Scala 2.8 breakOut

Somatik
sumber
7

Apa yang ingin Anda capai sedikit tidak terdefinisi.
Bagaimana jika dua atau lebih item yang cdibagikan sama p? Item mana yang akan dipetakan dengan yang ada pdi peta?

Cara yang lebih akurat untuk melihat ini adalah menghasilkan peta antara pdan semua citem yang memilikinya:

val m: Map[P, Collection[T]]

Ini dapat dengan mudah dicapai dengan groupBy :

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

Jika Anda masih menginginkan peta asli, Anda dapat, misalnya, memetakan pke yang pertama tmemilikinya:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }
Eyal Roth
sumber
1
Satu tweak berguna pada ini adalah dengan menggunakan collectbukan map. Misalnya: c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. Dengan cara ini Anda dapat melakukan hal-hal seperti meratakan peta ketika kunci Anda adalah Opsi [_].
healsjnr
@ healsjnr Tentu, ini bisa dikatakan untuk peta apa pun. Ini bukan masalah inti di sini.
Eyal Roth
1
Anda bisa menggunakan .mapValues(_.head)alih-alih peta.
lex82
2

Ini mungkin bukan cara yang paling efisien untuk mengubah daftar menjadi peta, tetapi itu membuat kode panggilan lebih mudah dibaca. Saya menggunakan konversi implisit untuk menambahkan metode mapBy ke Daftar:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

Contoh kode panggilan:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

Perhatikan bahwa karena konversi implisit, kode pemanggil perlu mengimpor konversi implisit scala.

Erez
sumber
2
c map (_.getP) zip c

Bekerja dengan baik dan sangat intuitif

Jörg Bächtiger
sumber
8
Silakan tambahkan lebih detail.
Syeda Zunaira
2
Maafkan saya. Tapi, ini ADA jawaban untuk pertanyaan "Scala cara terbaik mengubah Koleksi menjadi kunci-peta?" seperti Ben Lings.
Jörg Bächtiger
1
Dan Ben tidak memberikan penjelasan apa pun?
shinzou
1
ini membuat dua daftar dan bergabung menjadi "peta" menggunakan elemen-elemen csebagai kunci (semacam). Catatan "peta" karena koleksi yang dihasilkan bukan scala Maptetapi membuat daftar lain / itupable dari tuple ... tetapi efeknya sama untuk tujuan OP saya tidak akan mengabaikan kesederhanaan tetapi tidak seefisien foldLeftsolusi, juga tidak efisien jawaban sesungguhnya untuk pertanyaan "mengubah menjadi koleksi menjadi peta-oleh-kunci"
Dexter Legaspi
2

Bagaimana dengan menggunakan zip dan toMap?

myList.zip(myList.map(_.length)).toMap
KerenBobX64
sumber
1

Untuk apa nilainya, berikut adalah dua cara sia-sia melakukannya:

scala> case class Foo(bar: Int)
defined class Foo

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))

scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))
missingfaktor
sumber
Juga, fwiw, beginilah penampilan keduanya di Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor
-1

Ini bekerja untuk saya:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

Peta harus bisa berubah dan Peta harus kembali karena menambahkan ke Peta yang bisa berubah tidak mengembalikan peta.

rustyfinger
sumber
1
Sebenarnya, ini dapat diimplementasikan secara kekal sebagai berikut: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }Peta dapat berubah, seperti dibuktikan di atas, karena menambahkan ke Peta abadi mengembalikan Peta abadi baru dengan entri tambahan. Nilai ini berfungsi sebagai akumulator melalui operasi lipatan.
RamV13
-2

gunakan peta () pada koleksi diikuti dengan toMap

val map = list.map(e => (e, e.length)).toMap
Krishna Kumar Chourasiya
sumber
3
Bagaimana ini berbeda dari jawaban yang diajukan, dan diterima, 7 tahun yang lalu?
jwvh
-4

Jika mengkonversi dari Json String (membaca file json) ke scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)
Ajit Surendran
sumber
Pertanyaan berusia 11 tahun ini tidak menanyakan apa pun tentang JSON. Jawaban Anda salah.
jwvh