Saya mempelajari Haskell dan membaca beberapa artikel tentang perbedaan kinerja daftar Haskell dan (masukkan bahasa Anda).
Menjadi seorang pelajar saya jelas hanya menggunakan daftar tanpa memikirkan perbedaan kinerja. Saya baru-baru ini mulai menyelidiki dan menemukan banyak perpustakaan struktur data yang tersedia di Haskell.
Dapatkah seseorang tolong jelaskan perbedaan antara Daftar, Array, Vektor, Sekuens tanpa terlalu dalam dalam teori ilmu komputer tentang struktur data?
Juga, apakah ada beberapa pola umum di mana Anda akan menggunakan satu struktur data, bukan yang lain?
Apakah ada bentuk lain dari struktur data yang saya lewatkan dan mungkin bermanfaat?
Jawaban:
Daftar Rock
Sejauh ini struktur data yang paling bersahabat untuk data berurutan di Haskell adalah Daftar
Daftar memberi Anda ϴ (1) kontra dan pencocokan pola. Perpustakaan standar, dan untuk yang penting mendahului, penuh fungsi daftar berguna yang harus sampah kode Anda (
foldr
,map
,filter
). Daftar yang gigih , alias murni fungsional, yang sangat bagus. Daftar Haskell tidak benar-benar "daftar" karena mereka coinductive (bahasa lain menyebut stream ini) sehingga hal-hal sepertibekerja dengan luar biasa. Struktur data batuan tidak terbatas.
Daftar di Haskell menyediakan antarmuka seperti iterator dalam bahasa imperatif (karena kemalasan). Jadi, masuk akal bahwa mereka banyak digunakan.
Di samping itu
Masalah pertama dengan daftar adalah bahwa untuk mengindeks ke mereka
(!!)
membutuhkan waktu ϴ (k), yang menjengkelkan. Juga, penambahan bisa lambat++
, tetapi model evaluasi malas Haskell berarti bahwa ini dapat diperlakukan sebagai sepenuhnya diamortisasi, jika mereka terjadi sama sekali.Masalah kedua dengan daftar adalah mereka memiliki lokalitas data yang buruk. Prosesor nyata mengeluarkan konstanta tinggi ketika objek dalam memori tidak diletakkan bersebelahan. Jadi, di C ++
std::vector
memiliki "snoc" yang lebih cepat (meletakkan objek di akhir) daripada struktur data daftar terkait murni yang saya ketahui, meskipun ini bukan struktur data yang bertahan sehingga kurang ramah daripada daftar Haskell.Masalah ketiga dengan daftar adalah mereka memiliki efisiensi ruang yang buruk. Tumpukan pointer ekstra mendorong penyimpanan Anda (dengan faktor konstan).
Urutan Berfungsi
Data.Sequence
secara internal didasarkan pada pohon jari (saya tahu, Anda tidak ingin tahu ini) yang berarti bahwa mereka memiliki beberapa sifat yang bagusData.Sequence
adalah struktur data yang sepenuhnya tahan.Data.Sequence
paling lambat konstan.Di sisi lain,
Data.Sequence
tidak melakukan banyak hal untuk masalah lokalitas data, dan hanya berfungsi untuk koleksi terbatas (lebih sedikit malas daripada daftar)Array bukan untuk orang yang lemah hati
Array adalah salah satu struktur data paling penting dalam CS, tetapi mereka tidak cocok dengan dunia fungsional murni yang malas. Array menyediakan ϴ (1) akses ke tengah pengumpulan dan faktor lokalitas / faktor konstan yang sangat baik. Tapi, karena mereka tidak cocok dengan Haskell, mereka sulit digunakan. Sebenarnya ada banyak jenis array yang berbeda di pustaka standar saat ini. Ini termasuk array yang sepenuhnya tahan, array yang dapat diubah untuk monad IO, array yang dapat diubah untuk monad ST, dan versi yang tidak kotak dari yang di atas. Untuk lebih lanjut, periksa wiki haskell
Vektor adalah Array yang "lebih baik"
The
Data.Vector
paket menyediakan semua kebaikan array, di tingkat dan bersih yang lebih tinggi API. Kecuali Anda benar-benar tahu apa yang Anda lakukan, Anda harus menggunakan ini jika Anda membutuhkan kinerja seperti array. Tentu saja, beberapa peringatan masih berlaku - array bisa berubah seperti struktur data tidak bermain bagus dalam bahasa malas murni. Namun, kadang-kadang Anda menginginkan kinerja O (1), danData.Vector
memberikannya kepada Anda dalam paket yang bisa digunakan.Anda memiliki opsi lain
Jika Anda hanya ingin daftar dengan kemampuan untuk memasukkan secara efisien di akhir, Anda dapat menggunakan daftar perbedaan . Contoh terbaik dari daftar mengacaukan kinerja cenderung berasal dari
[Char]
mana pendahuluan telah disebut sebagaiString
.Char
daftar yang ramah, tetapi cenderung berjalan pada urutan 20 kali lebih lambat dari string C, jadi jangan ragu untuk menggunakanData.Text
atau sangat cepatData.ByteString
. Saya yakin ada perpustakaan berorientasi urutan lainnya yang tidak saya pikirkan saat ini.Kesimpulan
90 +% dari waktu saya perlu koleksi berurutan dalam daftar Haskell adalah struktur data yang tepat. Daftar seperti iterator, fungsi yang menggunakan daftar dapat dengan mudah digunakan dengan salah satu dari struktur data ini menggunakan
toList
fungsi yang menyertainya. Di dunia yang lebih baik, pendahuluan akan sepenuhnya parametrik untuk jenis wadah yang digunakannya, tetapi saat ini[]
mengotori perpustakaan standar. Jadi, menggunakan daftar (hampir) setiap tempat pasti baik-baik saja.Anda bisa mendapatkan versi parametrik sepenuhnya dari sebagian besar fungsi daftar (dan mulia untuk menggunakannya)
Bahkan,
Data.Traversable
mendefinisikan API yang kurang lebih bersifat universal di "daftar seperti" apa pun.Namun, meskipun Anda bisa menjadi baik dan hanya menulis kode parametrik sepenuhnya, kebanyakan dari kita tidak dan menggunakan daftar di semua tempat. Jika Anda belajar, saya sarankan Anda juga.
EDIT: Berdasarkan komentar saya menyadari saya tidak pernah menjelaskan kapan harus menggunakan
Data.Vector
vsData.Sequence
. Array dan Vektor menyediakan operasi pengindeksan dan pengirisan yang sangat cepat, tetapi pada dasarnya bersifat transien (penting) struktur data. Struktur data fungsional murni sukaData.Sequence
dan[]
biarkan secara efisien menghasilkan nilai baru dari nilai lama seolah-olah Anda telah memodifikasi nilai lama.tidak mengubah daftar lama, dan tidak perlu menyalinnya. Jadi meskipun
oldList
sangat panjang, "modifikasi" ini akan sangat cepat. Demikian pulaakan menghasilkan urutan baru dengan
newValue
untuk menggantikan elemen 3000-nya. Sekali lagi, itu tidak merusak urutan lama, itu hanya menciptakan yang baru. Tapi, ini sangat efisien, mengambil O (log (min (k, kn)) di mana n adalah panjang urutan, dan k adalah indeks yang Anda modifikasi.Anda tidak dapat dengan mudah melakukan ini dengan
Vectors
danArrays
. Mereka dapat dimodifikasi tetapi itu adalah modifikasi imperatif nyata, dan karena itu tidak dapat dilakukan dalam kode Haskell biasa. Itu berarti operasi dalamVector
paket yang membuat modifikasi sukasnoc
dancons
harus menyalin seluruh vektor jadi perluO(n)
waktu. Satu-satunya pengecualian untuk ini adalah bahwa Anda dapat menggunakan versi yang bisa berubah (Vector.Mutable
) di dalamST
monad (atauIO
) dan melakukan semua modifikasi Anda seperti yang Anda lakukan dalam bahasa imperatif. Setelah selesai, Anda "membekukan" vektor Anda untuk berubah menjadi struktur abadi yang ingin Anda gunakan dengan kode murni.Perasaan saya adalah bahwa Anda harus menggunakan default
Data.Sequence
jika daftar tidak sesuai. GunakanData.Vector
hanya jika pola penggunaan Anda tidak melibatkan banyak modifikasi, atau jika Anda memerlukan kinerja yang sangat tinggi dalam monad ST / IO.Jika semua pembicaraan tentang
ST
monad ini membuat Anda bingung: semakin banyak alasan untuk tetap berpegang teguh pada kecantikanData.Sequence
.sumber
[1..]
daftar di Haskell. Daftar juga dapat digunakan untuk hal-hal menyenangkan seperti mundur. Memikirkan mereka sebagai struktur kontrol (semacam) sangat membantu memahami bagaimana mereka digunakan.Data.Sequence
. Finger tree adalah salah satu penemuan paling mengagumkan dalam sejarah komputasi (Guibas mungkin akan mendapatkan penghargaan Turing suatu hari nanti) danData.Sequence
merupakan implementasi yang sangat baik dan memiliki API yang sangat bisa digunakan.import qualified Data.Vector.Unboxed as VU; main = print (VU.cons 'a' (VU.replicate 100 'b'))
mengkompilasi ke alokasi tunggal 404 byte (101 karakter) dalam Core: hpaste.org/65015