Znalazłem ten kod w podręczniku An Introduction to Programming in Emacs Lisp
demonstrującym rekurencję za pomocą cond
funkcji, aby dowiedzieć się o liczbie kamyków na podstawie wprowadzonej liczby wierszy, tj. Jeśli rzędy = 2, to kamyki powinny mieć 3, jeśli 4 rzędy, to powinno być 10 kamyków tam.
(defun triangle-using-cond (number)
(cond ((<= number 0) 0)
((= number 1) 1)
((> number 1)
(+ number (triangle-using-cond (1- number))))))
ocenić do 10 po przekazaniu argumentu 4:
(triangle-using-cond 4)
Podręcznik nie wyjaśnił jasno, co dzieje się na każdym etapie tego przykładowego kodu, a ja nie mogłem zrozumieć, jak działa rekurencja. Czy możesz mi pomóc krok po kroku zrozumieć mechanikę, co dzieje się w każdej instancji?
triangle-using-cond
argumentem jest 1 mniej niż dowolna liczba. Warunki są uporządkowane w kolejności a, b, a następnie c - niezależnie od tego, co pasuje najpierw, kończy się buck.1-4 = 3
. Teraz będzie to połączenie rekurencyjne(triangle-using-cond 3)
, ale skończy się to tym samym połączeniem cyklicznym, dopóki nie osiągnie 1 warunkowego, prawda? co się później stanie?(triangle-using-cond 3)
?1-
ma szczególnie mylącą nazwę, zwłaszcza jeśli odczytujesz połączenie tak, jakby to była notacja infiksowa. Zwraca swój argument minus jeden; NIE jeden minus argument.Odpowiedzi:
Korzystanie z „debugowania printf”
Możesz pozwolić Emacsowi pomóc w zrozumieniu, modyfikując definicję funkcji:
Po prostu dodaj
(message ...)
gdzieś, aby wydrukować ślad do*Messages*
bufora.Korzystanie z Edebug
Umieść punkt w dowolnym miejscu definicji funkcji i wciśnij,
C-u C-M-x
aby ją „instrumentować”. Następnie oceń funkcję, np. Umieszczając punkt po(triangle-using-cond 3)
i naciskającC-x C-e
.Jesteś teraz w trybie Edebug. Naciśnij spację, aby przejść przez funkcję. Wartości pośrednie każdego wyrażenia są pokazane w obszarze echa. Aby wyjść z trybu Edebug, wystarczy nacisnąć
q
. Aby usunąć oprzyrządowanie, umieść punkt w dowolnym miejscu definicji i naciśnij,C-M-x
aby ponownie ocenić definicję.Korzystanie ze standardowego debugera Emacs
M-x debug-on-entry triangle-using-cond
, potriangle-using-cond
uruchomieniu zostanie umieszczony w debuggerze Emacsa (buforze*Backtrace*
).Przejdź przez ewaluację, używając
d
(lubc
pomiń wszelkie nieciekawe oceny).Aby zobaczyć stan pośredni (wartości zmiennych itp.), Możesz użyć w
e
dowolnym momencie. Zostaniesz poproszony o wprowadzenie sexp w celu oceny, a wynik oceny zostanie wydrukowany.Podczas korzystania z debugera trzymaj kopię kodu źródłowego widoczną w innej ramce, abyś mógł śledzić, co się dzieje.
Możesz także wstawić jawne wywołania, aby wprowadzić debugger (mniej lub bardziej punkty przerwania) w dowolnych miejscach w kodzie źródłowym. Wstawiasz
(debug)
lub(debug nil SOME-SEXP-TO-EVALUATE)
. W tym drugim przypadku, po wprowadzeniu debuggeraSOME-SEXP-TO-EVALUATE
jest oceniany i wynik jest drukowany. (Pamiętaj, że możesz wstawić taki kod do kodu źródłowego i użyć goC-M-x
do oceny, a następnie cofnąć - nie musisz zapisywać edytowanego pliku).Aby
Using Debugger
uzyskać więcej informacji, zobacz instrukcję Elisp, węzeł .Rekurencja jako pętla
W każdym razie pomyśl o rekurencji jako o pętli. Zdefiniowano dwa przypadki zakończenia:
(<= number 0)
i(= number 1)
. W takich przypadkach funkcja zwraca liczbę prostą.W przypadku rekurencyjnym funkcja zwraca sumę tej liczby i wynik funkcji za pomocą
number - 1
. Ostatecznie funkcja zostanie wywołana z1
liczbą lub liczbą mniejszą lub równą zero.Wynik przypadku rekurencyjnego jest zatem następujący:
Weźmy na przykład
(triangle-using-cond 4)
. Skumulujmy końcowe wyrażenie:w pierwszej iteracji
number
jest4
, więc(> number 1)
gałąź jest śledzona. Zaczynamy budować wyrażenie(+ 4 ...
i wywołujemy funkcję za pomocą(1- 4)
, tj(triangle-using-cond 3)
.teraz
number
jest3
, a wynik jest(+ 3 (triangle-using-cond 2))
. Łączne wyrażenie wyniku to(+ 4 (+ 3 (triangle-using-cond 2)))
.number
jest2
teraz, więc wyrażenie jest(+ 4 (+ 3 (+ 2 (triangle-using-cond 1))))
number
jest1
teraz i bierzemy(= number 1)
gałąź, co powoduje nudę1
. Całe wyrażenie brzmi(+ 4 (+ 3 (+ 2 1)))
. Ocenia, że od wewnątrz na zewnątrz i masz:(+ 4 (+ 3 3))
,(+ 4 6)
lub po prostu10
.źródło
message (...)
, naciśnięcieC-x C-e
pokazuje tylko końcowy wynik (10) nic więcej? Czy coś brakuje?Edebug
w życie?C-u C-M-x
z punktem wewnątrz funkcji, aby go edytować. Następnie uruchom normalną funkcję.(message ...)
rzeczy drukuje do*Message*
bufora.Model zastępowania aplikacji procedury z SICP może objaśnić algorytm tak, aby rozumiał taki kod.
Napisałem również kod, aby to ułatwić. robi to
lispy-flatten
z pakietu lispy . Oto wynik zastosowanialispy-flatten
do(triangle-using-cond 4)
:Możesz uprościć powyższe wyrażenie do:
Następnie spłaszcz jeszcze raz:
Wynik końcowy:
źródło
Nie jest to specyficzne dla Emacsa / Elisp, ale jeśli masz doświadczenie matematyczne, rekurencja jest jak indukcja matematyczna . (Lub jeśli nie: to kiedy uczysz się indukcji, jest to jak rekurencja!)
Zacznijmy od definicji:
Kiedy
number
jest4
, żaden z dwóch pierwszych warunków nie jest uwzględniany, więc jest on oceniany zgodnie z trzecim warunkiem:(triangle-using-cond 4)
jest oceniany jako(+ number (triangle-using-cond (1- number)))
, a mianowicie jako(+ 4 (triangle-using-cond 3))
.Podobnie
(triangle-using-cond 3)
jest oceniany jako(+ 3 (triangle-using-cond 2))
.Podobnie
(triangle-using-cond 2)
jest oceniany jako(+ 2 (triangle-using-cond 1))
.Ale dla
(triangle-using-cond 1)
drugiego warunku obowiązuje i jest on oceniany jako1
.Porada dla każdego, kto uczy się rekurencji: staraj się unikać
Jeśli próbujesz przekonać się, czy
(triangle-using-cond 4)
zwróci prawidłową odpowiedź, po prostu załóż, że(triangle-using-cond 3)
zwróci właściwą odpowiedź, i sprawdź, czy w takim przypadku będzie poprawna. Oczywiście musisz również zweryfikować skrzynkę podstawową.źródło
Kroki obliczeniowe dla Twojego przykładu wyglądałyby następująco:
Warunek 0 w rzeczywistości nigdy nie jest spełniony, ponieważ 1 jako wejście już kończy rekurencję.
źródło
(1)
nie jest prawidłowym wyrażeniem.M-x calc
. :-) Poważnie, chciałem jednak pokazać obliczenia, a nie ocenę Lisp.(4 +
zamiast(+ 4
w twojej odpowiedzi ... :)Myślę, że jest to dość łatwe, nie potrzebujesz emacs lisp pod tym, to tylko matematyka w szkole podstawowej.
f (0) = 0
f (1) = 1
f (n) = f (n-1) + n, gdy n> 1
więc f (5) = 5 + f (4) = 5 + 4 + f (3) = 5 + 4 + 3 + 2 + 1 + 0
Teraz to oczywiste.
źródło