Saya menemukan kode ini dalam manual yang An Introduction to Programming in Emacs Lisp
menunjukkan rekursi dengan bantuan cond
fungsi untuk mengetahui jumlah kerikil berdasarkan jumlah baris yang dimasukkan, yaitu jika baris = 2, maka kerikil harus 3, jika 4 baris maka harus 10 kerikil sana.
(defun triangle-using-cond (number)
(cond ((<= number 0) 0)
((= number 1) 1)
((> number 1)
(+ number (triangle-using-cond (1- number))))))
mengevaluasi sampai 10 setelah melewati argumen 4:
(triangle-using-cond 4)
Manual tidak menjelaskan dengan jelas apa yang terjadi pada setiap langkah dalam kode contoh ini khususnya dan saya tidak tahu bagaimana rekursi bekerja di sini. Dapatkah Anda membantu saya memahami mekanika langkah demi langkah apa yang terjadi pada setiap contoh?
triangle-using-cond
dengan argumen menjadi 1 kurang dari apa pun jumlahnya. Kondisi berjalan dalam urutan a, b, dan kemudian c - apa pun yang cocok terlebih dahulu, adalah di mana uang berhenti.1-4 = 3
,. Sekarang panggilan rekursif akan(triangle-using-cond 3)
, tetapi itu akan berakhir dengan panggilan rekursif yang sama berulang-ulang sampai mencapai 1 kondisi, kan? apa yang akan terjadi selanjutnya?(triangle-using-cond 3)
?1-
memiliki nama yang sangat menyesatkan, terutama jika Anda membaca panggilan seolah-olah itu notasi infiks. Ia mengembalikan argumennya dikurangi satu; BUKAN satu minus argumen.Jawaban:
Menggunakan "printf debugging"
Anda dapat membiarkan Emacs membantu Anda memahami dengan memodifikasi definisi fungsi:
Cukup tambahkan
(message ...)
tempat untuk membuat jejak dicetak ke*Messages*
buffer.Menggunakan Edebug
Tempatkan titik di mana saja di dalam definisi fungsi dan tekan
C-u C-M-x
untuk "instrumen" itu. Kemudian evaluasi fungsi, misalnya dengan menempatkan titik setelah(triangle-using-cond 3)
dan memukulC-x C-e
.Anda sekarang dalam mode Edebug. Tekan bilah spasi untuk melangkah melalui fungsi. Nilai menengah dari setiap ekspresi ditampilkan di area gema. Untuk keluar dari mode Edebug, tekan saja
q
. Untuk menghapus instrumentasi, beri titik di mana saja di dalam definisi dan tekanC-M-x
untuk mengevaluasi kembali definisi.Menggunakan debugger Emacs standar
M-x debug-on-entry triangle-using-cond
, kemudian, ketikatriangle-using-cond
dipanggil, Anda ditempatkan di debugger Emacs (buffer*Backtrace*
).Langkah melalui evaluasi menggunakan
d
(atauc
untuk melewati evaluasi yang tidak menarik).Untuk melihat kondisi antara (nilai variabel, dll.) Anda dapat menggunakan
e
kapan saja. Anda diminta memasukkan sexp untuk mengevaluasi, dan hasil evaluasi dicetak.Saat Anda menggunakan debugger, buat salinan kode sumber terlihat di frame lain, sehingga Anda dapat mengikuti apa yang terjadi.
Anda juga dapat memasukkan panggilan eksplisit untuk memasukkan debugger (lebih atau kurang breakpoints) di tempat sewenang-wenang dalam kode sumber. Anda memasukkan
(debug)
atau(debug nil SOME-SEXP-TO-EVALUATE)
. Dalam kasus terakhir, ketika debugger dimasukkanSOME-SEXP-TO-EVALUATE
dievaluasi dan hasilnya dicetak. (Ingatlah bahwa Anda dapat memasukkan kode seperti itu ke dalam kode sumber dan menggunakannyaC-M-x
untuk mengevaluasinya, lalu batalkan - Anda tidak perlu menyimpan file yang diedit.)Lihat manual Elisp, simpul
Using Debugger
untuk informasi lebih lanjut.Rekursi sebagai lingkaran
Bagaimanapun, anggap rekursi sebagai satu lingkaran. Ada dua kasus terminasi yang didefinisikan:
(<= number 0)
dan(= number 1)
. Dalam kasus ini fungsi mengembalikan angka sederhana.Dalam kasus rekursif fungsi mengembalikan jumlah dari angka itu dan hasil dari fungsi dengan
number - 1
. Akhirnya, fungsi akan dipanggil dengan salah satu1
atau nomor yang lebih kecil dari atau sama dengan nol.Karenanya, hasil kasus rekursif adalah:
Ambil contoh
(triangle-using-cond 4)
. Mari kita akumulasikan ekspresi terakhir:di iterasi pertama
number
adalah4
, sehingga(> number 1)
cabang diikuti. Kami mulai membangun ekspresi(+ 4 ...
dan memanggil fungsi dengan(1- 4)
, yaitu(triangle-using-cond 3)
.sekarang
number
adalah3
, dan hasilnya adalah(+ 3 (triangle-using-cond 2))
. Ekspresi hasil total adalah(+ 4 (+ 3 (triangle-using-cond 2)))
.number
adalah2
sekarang, jadi ekspresi adalah(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))
number
adalah1
sekarang, dan kita mengambil(= number 1)
cabang, sehingga membosankan sebuah1
. Seluruh ekspresi itu(+ 4 (+ 3 (+ 2 1)))
. Evaluasi yang dari dalam ke luar dan Anda mendapatkan:(+ 4 (+ 3 3))
,(+ 4 6)
, atau hanya10
.sumber
message (...)
, memukulC-x C-e
hanya menunjukkan hasil akhir (10) tidak ada yang lain? Apakah saya melewatkan sesuatu?Edebug
beraksi?C-u C-M-x
dengan titik di dalam fungsi untuk edebug. Kemudian jalankan saja fungsinya seperti biasa.(message ...)
barang yang dicetak ke*Message*
buffer.Model Substitusi untuk Prosedur Aplikasi dari SICP dapat menjelaskan algoritma untuk memahami kode seperti ini.
Saya menulis beberapa kode untuk memfasilitasi ini juga.
lispy-flatten
dari paket lispy melakukan ini. Inilah hasil melamarlispy-flatten
ke(triangle-using-cond 4)
:Anda dapat menyederhanakan ungkapan di atas menjadi hanya:
Kemudian ratakan sekali lagi:
Hasil akhir:
sumber
Ini tidak spesifik untuk Emacs / Elisp, tetapi jika Anda memiliki latar belakang matematika, maka rekursi seperti induksi matematika . (Atau jika Anda tidak: maka ketika Anda belajar induksi, itu seperti rekursi!)
Mari kita mulai dengan definisi:
Ketika
number
ini4
, tak satu pun dari dua kondisi pertama terus, jadi itu dievaluasi sesuai dengan kondisi ketiga:(triangle-using-cond 4)
dievaluasi sebagai(+ number (triangle-using-cond (1- number)))
, yaitu sebagai(+ 4 (triangle-using-cond 3))
.Demikian pula,
(triangle-using-cond 3)
dievaluasi sebagai(+ 3 (triangle-using-cond 2))
.Demikian pula,
(triangle-using-cond 2)
dievaluasi sebagai(+ 2 (triangle-using-cond 1))
.Tetapi untuk
(triangle-using-cond 1)
, kondisi kedua berlaku, dan itu dievaluasi sebagai1
.Sepotong saran bagi siapa pun yang mempelajari rekursi: cobalah untuk menghindarinya
Jika Anda mencoba meyakinkan diri sendiri apakah
(triangle-using-cond 4)
akan mengembalikan jawaban yang benar, anggap saja itu(triangle-using-cond 3)
akan mengembalikan jawaban yang benar, dan verifikasi apakah jawaban itu akan benar dalam kasus itu. Tentu saja Anda harus memverifikasi casing dasar juga.sumber
Langkah-langkah perhitungan untuk contoh Anda akan seperti berikut:
Kondisi 0 sebenarnya tidak pernah dipenuhi karena 1 sebagai input sudah mengakhiri rekursi.
sumber
(1)
bukan ekspresi yang valid.M-x calc
. :-) Serius, saya bermaksud menunjukkan perhitungan, bukan evaluasi Lisp.(4 +
bukan(+ 4
jawabanmu ... :)Saya pikir itu cukup mudah, Anda tidak perlu emacs lisp di bawah ini, itu hanya matematika sekolah dasar.
f (0) = 0
f (1) = 1
f (n) = f (n-1) + n saat n> 1
jadi f (5) = 5 + f (4) = 5 + 4 + f (3) = 5 + 4 + 3 + 2 + 1 + 0
Sekarang sudah jelas.
sumber