Rangkuman daftar scala, ::: vs ++


362

Apakah ada perbedaan di antara keduanya ::: dan ++untuk daftar gabungan di Scala?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

Dari dokumentasi sepertinya ++lebih umum sedangkan :::yaituList spesifik. Apakah yang terakhir disediakan karena digunakan dalam bahasa fungsional lainnya?


4
Juga :::merupakan operator awalan seperti semua metode yang dimulai dengan:
Ben Jackson

3
Jawabannya cukup banyak menggambarkan bagaimana scala dikembangkan di sekitar daftar dan keseragaman operator di Scala (atau kurangnya yang terakhir). Sangat disayangkan bahwa sesuatu yang begitu sederhana memiliki ekor kecil yang panjang untuk membingungkan dan membuang waktu setiap pelajar Scala. Saya berharap itu akan mendatar di 2.12.
matanster

Jawaban:


321

Warisan. Daftar awalnya didefinisikan sebagai fungsional-mencari bahasa:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

Tentu saja, Scala mengembangkan koleksi lain, secara ad-hoc. Ketika 2,8 keluar, koleksi yang dirancang ulang untuk digunakan kembali kode maksimum dan API konsisten, sehingga Anda dapat menggunakan ++untuk menggabungkan setiap dua koleksi - dan bahkan iterator. Daftar, bagaimanapun, harus menjaga operator aslinya, selain dari satu atau dua yang sudah usang.


19
Jadi apakah itu praktik terbaik untuk menghindari :::mendukung ++sekarang? Juga gunakan +:bukan ::?
Luigi Plinge

37
::berguna karena pencocokan pola (lihat Daniel contoh kedua). Anda tidak dapat melakukannya dengan+:
paradigma

1
@Luigi Jika Anda menggunakan Listalih-alih Seq, Anda sebaiknya menggunakan Listmetode idiomatik . Di sisi lain, itu akan membuat lebih sulit untuk berubah ke tipe lain, jika Anda ingin melakukannya.
Daniel C. Sobral

2
Saya merasa baik bahwa seseorang memiliki kedua operasi idiomatik Daftar (seperti ::dan :::) dan operasi yang lebih umum yang umum untuk koleksi lainnya. Saya tidak akan membatalkan operasi dari bahasa.
Giorgio

21
@paradigmatic Scala 2.10 memiliki :+dan mengekstraksi +:objek.
0__

97

Selalu gunakan :::. Ada dua alasan: efisiensi dan keamanan tipe.

Efisiensi

x ::: y ::: zlebih cepat daripada x ++ y ++ z, karena :::asosiatif yang benar. x ::: y ::: zdiuraikan sebagai x ::: (y ::: z), yang secara algoritmik lebih cepat dari (x ::: y) ::: z(yang terakhir membutuhkan O (| x |) langkah lebih lanjut).

Ketik keamanan

Dengan :::Anda hanya dapat menggabungkan dua Lists. Dengan ++Anda dapat menambahkan koleksi apa pun ke List, yang mengerikan:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++juga mudah digabungkan dengan +:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
Ketika menggabungkan hanya 2 daftar, tidak ada perbedaan, tetapi dalam kasus 3 atau lebih, Anda memiliki poin bagus, dan saya mengonfirmasinya dengan tolok ukur cepat. Namun, jika Anda khawatir dengan efisiensi, x ::: y ::: zsebaiknya diganti dengan List(x, y, z).flatten. pastebin.com/gkx7Hpad
Luigi Plinge

3
Tolong jelaskan, mengapa penggabungan asosiatif kiri memerlukan lebih banyak langkah O (x). Saya pikir keduanya bekerja untuk O (1).
pacman

6
@pacman Lists secara tunggal ditautkan, untuk menambahkan satu daftar ke yang lain, Anda perlu membuat salinan dari daftar pertama yang memiliki yang kedua dilampirkan di bagian akhir. Oleh karena itu, penggabungan adalah O (n) sehubungan dengan jumlah elemen dalam daftar pertama. Panjang daftar kedua tidak mempengaruhi runtime sehingga lebih baik untuk menambahkan daftar panjang ke daftar pendek daripada menambahkan daftar pendek ke daftar panjang.
puhlen

1
Daftar @pacman Scala tidak dapat diubah . Itu sebabnya kita tidak bisa begitu saja mengganti tautan terakhir saat melakukan penggabungan. Kita harus membuat daftar baru dari awal.
ZhekaKozlov

4
@ Pasac Kompleksitasnya selalu linear wrt panjang xdan y( ztidak pernah iterated dalam hal apapun sehingga tidak berpengaruh pada waktu berjalan, ini sebabnya lebih baik untuk menambahkan daftar panjang ke yang pendek, daripada sebaliknya) tetapi kompleksitas asimptotik tidak menceritakan keseluruhan cerita. x ::: (y ::: z)mengulang ydan menambahkan z, lalu mengulang xdan menambahkan hasil y ::: z. xdan ykeduanya diulang sekali. (x ::: y) ::: zmengulangi xdan menambahkan y, lalu mengulangi hasil dari x ::: ydan menambahkan z. ymasih diulang satu kali tetapi xdiulang dua kali dalam kasus ini.
puhlen

84

:::hanya bekerja dengan daftar, sementara ++dapat digunakan dengan traversable apa pun. Dalam implementasi saat ini (2.9.0), ++kembali aktif :::jika argumennya juga a List.


4
Jadi sangat mudah untuk menggunakan ::: dan ++ bekerja dengan daftar. Itu berpotensi dapat membuat kekacauan pada kode / gaya.
ses

24

Poin yang berbeda adalah kalimat pertama diurai sebagai:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

Sedangkan contoh kedua diuraikan sebagai:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

Jadi, jika Anda menggunakan makro, Anda harus berhati-hati.

Selain itu, ++untuk dua daftar memanggil::: tetapi dengan lebih banyak overhead karena meminta nilai implisit untuk memiliki pembangun dari Daftar ke Daftar. Tapi microbenchmarks tidak membuktikan sesuatu yang berguna dalam arti itu, saya kira kompiler mengoptimalkan panggilan tersebut.

Tolok Ukur Mikro setelah pemanasan.

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

Seperti yang dikatakan Daniel C. Sobrai, Anda dapat menambahkan konten dari koleksi apa pun ke daftar menggunakan ++, sedangkan dengan :::Anda hanya dapat menggabungkan daftar.


20
Harap posting microbenchmarks Anda yang tidak terlalu sederhana dan saya akan memberikan suaranya.
Mikaël Mayer
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.