Saya perhatikan bahwa sebagian besar bahasa fungsional menggunakan daftar yang terhubung sendiri (daftar "kontra") sebagai jenis daftar yang paling mendasar. Contohnya termasuk Common Lisp, Haskell dan F #. Ini berbeda dengan bahasa umum, di mana jenis daftar asli adalah array.
Mengapa demikian?
Untuk Common Lisp (diketik secara dinamis) saya mendapatkan ide bahwa kontra cukup umum untuk menjadi basis daftar, pohon, dll. Ini mungkin merupakan alasan kecil.
Untuk bahasa yang diketik secara statis, saya tidak dapat menemukan alasan yang bagus, saya bahkan dapat menemukan argumen-kontra:
- Gaya fungsional mendorong kekekalan, sehingga kemudahan penyisipan daftar yang ditautkan kurang menguntungkan,
- Gaya fungsional mendorong kekekalan, demikian juga berbagi data; sebuah array lebih mudah untuk dibagikan "sebagian" dari daftar yang ditautkan,
- Anda dapat melakukan pencocokan pola pada array biasa juga, dan bahkan lebih baik (Anda dapat dengan mudah melipat dari kanan ke kiri misalnya),
- Selain itu, Anda mendapatkan akses acak gratis,
- Dan (keuntungan praktis) jika bahasa diketik secara statis, Anda dapat menggunakan tata letak memori biasa dan mendapatkan peningkatan kecepatan dari cache.
Jadi mengapa memilih daftar yang ditautkan?
an array is easier to share "partially" than a linked list
perlu klarifikasi untuk apa yang Anda maksud. Karena sifat rekursif mereka, yang terjadi adalah kebalikannya seperti yang saya pahami - Anda dapat sebagian membagi daftar yang tertaut dengan lebih mudah dengan meneruskan simpul apa pun di dalamnya, sementara sebuah array perlu menghabiskan waktu membuat salinan baru. Atau dalam hal berbagi data, dua daftar tertaut dapat menunjuk ke akhiran yang sama, yang jelas tidak mungkin dengan array.Jawaban:
Faktor yang paling penting adalah bahwa Anda dapat menambahkan ke daftar tunggal yang tidak dapat diubah dalam waktu O (1), yang memungkinkan Anda untuk secara rekursif membangun daftar elemen-n dalam waktu O (n) seperti ini:
Jika Anda melakukan ini menggunakan array tetap, runtime akan menjadi kuadrat karena setiap
cons
operasi akan perlu menyalin seluruh array, yang mengarah ke waktu berjalan kuadratik.Saya berasumsi dengan berbagi "sebagian" yang Anda maksudkan bahwa Anda dapat mengambil subarray dari array dalam waktu O (1), sedangkan dengan daftar tertaut Anda hanya dapat mengambil ekor dalam waktu O (1) dan yang lainnya membutuhkan O (n). Itu benar.
Namun mengambil ekor sudah cukup dalam banyak kasus. Dan Anda harus memperhitungkan bahwa membuat subarrays yang murah tidak akan membantu Anda jika Anda tidak punya cara membuat array yang murah. Dan (tanpa optimisasi kompiler yang cerdas) tidak ada cara murah untuk membangun array langkah demi langkah.
sumber
Saya pikir ini turun ke daftar yang agak mudah diimplementasikan dalam kode fungsional.
Skema:
Haskell:
Array lebih sulit dan hampir tidak cantik untuk diimplementasikan. Jika Anda ingin mereka menjadi sangat cepat maka mereka harus ditulis tingkat rendah.
Selain itu, rekursi bekerja jauh lebih baik pada daftar daripada array. Pertimbangkan berapa kali Anda secara rekursif mengonsumsi / membuat daftar vs array yang diindeks.
sumber
Daftar yang terhubung sendiri adalah struktur data persisten yang paling sederhana .
Struktur data yang persisten sangat penting untuk performan, pemrograman murni fungsional.
sumber
Anda dapat menggunakan node Cons dengan mudah hanya jika Anda memiliki bahasa sampah yang dikumpulkan.
Node Kontra sangat cocok dengan gaya pemrograman fungsional panggilan rekursif dan nilai-nilai yang tidak berubah. Jadi sangat cocok dengan model mental programmer.
Dan jangan lupakan alasan historis. Mengapa mereka masih disebut Node Kontra dan lebih buruk lagi menggunakan mobil dan cdr sebagai pengakses? Orang-orang mempelajarinya dari buku pelajaran dan kursus lalu menggunakannya.
Anda benar, dalam array dunia nyata jauh lebih mudah digunakan, hanya mengkonsumsi setengah ruang memori dan jauh lebih berkinerja karena tingkat cache yang meleset. Tidak ada alasan untuk menggunakannya dengan bahasa imperatif.
sumber
Daftar tertaut penting karena alasan berikut:
Setelah Anda mengambil angka seperti 3, dan mengonversinya menjadi urutan penerus seperti
succ(succ(succ(zero)))
, dan kemudian menggunakan substitusi dengannya{succ=List node with some memory space}
, dan{zero = end of list}
, Anda akan berakhir dengan daftar tertaut (panjang 3).Bagian penting sebenarnya adalah angka, substitusi, dan ruang memori dan nol.
sumber