Saya belajar pemrograman fungsional dengan Haskell . Sementara itu saya sedang belajar teori Automata dan karena keduanya tampaknya cocok bersama saya sedang menulis perpustakaan kecil untuk bermain dengan automata.
Inilah masalah yang membuat saya mengajukan pertanyaan. Saat mempelajari cara untuk mengevaluasi jangkauan suatu negara saya mendapat ide bahwa algoritma rekursif sederhana akan sangat tidak efisien, karena beberapa jalur mungkin berbagi beberapa keadaan dan saya mungkin akhirnya mengevaluasinya lebih dari sekali.
Sebagai contoh, di sini, mengevaluasi reachability dari g dari sebuah , aku harus mengecualikan f baik saat memeriksa jalur melalui d dan c :
Jadi ide saya adalah suatu algoritma yang bekerja secara paralel pada banyak jalur dan memperbarui catatan bersama dari negara yang dikecualikan mungkin bagus, tapi itu terlalu banyak bagi saya.
Saya telah melihat bahwa dalam beberapa kasus rekursi sederhana seseorang dapat menyatakan status sebagai argumen, dan itulah yang harus saya lakukan di sini, karena saya meneruskan daftar status yang telah saya lalui untuk menghindari loop. Tetapi apakah ada cara untuk melewati daftar itu juga mundur, seperti mengembalikannya dalam tuple bersama dengan hasil boolean dari canReach
fungsi saya ? (Meskipun ini terasa agak dipaksakan)
Selain keabsahan contoh kasus saya , teknik apa yang tersedia untuk menyelesaikan masalah semacam ini? Saya merasa ini harus cukup umum sehingga harus ada solusi seperti apa yang terjadi dengan fold*
atau map
.
Sejauh ini, membaca learnyouahaskell.com saya belum menemukannya, tetapi anggap saya belum menyentuh monad.
( jika tertarik, saya memposting kode saya di codereview )
sumber
Jawaban:
Pemrograman fungsional tidak menghilangkan status. Itu hanya membuatnya eksplisit! Meskipun benar bahwa fungsi seperti peta akan sering "mengurai" struktur data "bersama", jika semua yang ingin Anda lakukan adalah menulis algoritma reachability maka itu hanya masalah melacak node apa yang sudah Anda kunjungi:
Sekarang, saya harus mengakui bahwa melacak semua keadaan ini dengan tangan cukup menjengkelkan dan rawan kesalahan (mudah digunakan s 'bukan s' ', mudah untuk melewati yang sama' ke lebih dari satu perhitungan ...) . Di sinilah monad masuk: mereka tidak menambahkan apa pun yang belum bisa Anda lakukan sebelumnya tetapi mereka membiarkan Anda melewati variabel keadaan sekitar secara implisit dan antarmuka menjamin bahwa hal itu terjadi dalam cara single-threaded.
Sunting: Saya akan berusaha memberikan alasan lebih banyak tentang apa yang saya lakukan sekarang: pertama-tama, alih-alih hanya menguji tingkat pencapaian, saya mengode pencarian mendalam-pertama. Implementasinya akan terlihat hampir sama tetapi debugging terlihat sedikit lebih baik.
Dalam bahasa yang stateful, DFS akan terlihat seperti ini:
Sekarang kita perlu menemukan cara untuk menyingkirkan keadaan yang bisa berubah. Pertama-tama kita menyingkirkan variabel "daftar kunjungan" dengan membuat df mengembalikannya sebagai ganti batal:
Dan sekarang sampai pada bagian yang sulit: menyingkirkan variabel "yang dikunjungi". Trik dasarnya adalah dengan menggunakan konvensi di mana kita melewati negara sebagai parameter tambahan untuk fungsi yang membutuhkannya dan meminta fungsi-fungsi mengembalikan versi baru negara sebagai nilai pengembalian tambahan jika mereka ingin memodifikasinya.
Untuk menerapkan pola ini ke dfs, kita perlu mengubahnya untuk menerima set "dikunjungi" sebagai parameter tambahan dan untuk mengembalikan versi terbaru dari "dikunjungi" sebagai nilai pengembalian ekstra. Selain itu, kita perlu menulis ulang kode sehingga kita selalu meneruskan versi "terbaru" dari array yang "dikunjungi":
Versi Haskell cukup banyak melakukan apa yang saya lakukan di sini, kecuali bahwa itu berjalan sepanjang jalan dan menggunakan fungsi rekursif dalam bukannya variabel "curr_visited" dan "childtrees" yang bisa diubah.
Sedangkan untuk monad, apa yang mereka capai pada dasarnya adalah secara implisit mengedarkan "curr_visited", bukannya memaksa Anda melakukannya dengan tangan. Ini tidak hanya menghapus kekacauan dari kode, tetapi juga mencegah Anda melakukan kesalahan, seperti keadaan forking (meneruskan "kunjungan" yang sama ke dua panggilan berikutnya alih-alih mengubah status).
sumber
Inilah jawaban sederhana yang diandalkan
mapConcat
.Di mana
neighbors
mengembalikan negara yang segera terhubung ke suatu negara. Ini mengembalikan serangkaian jalur.sumber