Wygląda na to, że znalazłem ogólny sposób na konwersję dowolnej procedury rekurencyjnej na rekurencyjną:
- Zdefiniuj podprocedurę pomocnika z dodatkowym parametrem „wynik”.
- Zastosuj to, co zostanie zastosowane do wartości zwracanej przez procedurę do tego parametru.
- Zadzwoń do tej procedury pomocnika, aby rozpocząć. Wartość początkowa parametru „wynik” jest wartością punktu wyjścia procesu rekurencyjnego, tak że wynikowy proces iteracyjny rozpoczyna się od miejsca, w którym proces rekurencyjny zaczyna się kurczyć.
Na przykład tutaj jest oryginalna procedura rekurencyjna do konwersji ( ćwiczenie SICP 1.17 ):
(define (fast-multiply a b)
(define (double num)
(* num 2))
(define (half num)
(/ num 2))
(cond ((= b 0) 0)
((even? b) (double (fast-multiply a (half b))))
(else (+ (fast-multiply a (- b 1)) a))))
Oto konwertowana procedura rekurencyjna ( ćwiczenie SICP 1.18 ):
(define (fast-multiply a b)
(define (double n)
(* n 2))
(define (half n)
(/ n 2))
(define (multi-iter a b product)
(cond ((= b 0) product)
((even? b) (multi-iter a (half b) (double product)))
(else (multi-iter a (- b 1) (+ product a)))))
(multi-iter a b 0))
Czy ktoś może to udowodnić lub obalić?
algorithms
logic
recursion
lisp
nalzok
źródło
źródło
b
bycia potęgą 2 pokazuje, że początkowo ustawienieproduct
na 0 nie jest całkiem właściwe; ale zmiana na 1 nie działa, gdyb
jest dziwna. Może potrzebujesz 2 różnych parametrów akumulatora?Odpowiedzi:
Twój opis algorytmu jest naprawdę zbyt niejasny, aby go ocenić w tym momencie. Ale oto kilka rzeczy do rozważenia.
CPS
W rzeczywistości istnieje sposób na przekształcenie dowolnego kodu w formę, która używa tylko wywołań tail. To jest transformacja CPS. CPS ( Continuation-Passing Style ) to forma wyrażania kodu poprzez przekazywanie każdej funkcji kontynuacji. Kontynuacja jest abstrakcyjnym pojęciem reprezentującym „resztę obliczeń”. W kodzie wyrażonym w postaci CPS naturalnym sposobem potwierdzenia kontynuacji jest funkcja, która przyjmuje wartość. W CPS zamiast funkcji zwracającej wartość, stosuje funkcję reprezentującą bieżącą kontynuację do bycia „zwróconym” przez funkcję.
Rozważmy na przykład następującą funkcję:
Można to wyrazić w CPS w następujący sposób:
Jest brzydka i często powolna, ale ma pewne zalety:
TCO
Wydaje mi się, że jedynym powodem, dla którego należy zajmować się rekurencją ogona (lub ogólnie ogłaszaniem ogonów), jest dla celów optymalizacji ogonów ogonowych (TCO). Wydaje mi się, że lepszym pytaniem jest „czy mój kod wydajności transformacji, który można zoptymalizować na żądanie?”.
Jeśli jeszcze raz rozważymy CPS, jedną z jego cech jest to, że kod wyrażony w CPS składa się wyłącznie z wywołań tail. Ponieważ wszystko jest na żądanie, nie musimy zapisywać punktu zwrotu na stosie. Więc cały kod w postaci CPS musi być zoptymalizowany pod kątem wywołania ogona, prawda?
Cóż, niezupełnie. Widzisz, chociaż może się wydawać, że wyeliminowaliśmy stos, wszystko, co zrobiliśmy, to po prostu zmiana sposobu, w jaki go reprezentujemy. Stos jest teraz częścią zamknięcia reprezentującego kontynuację. Więc CPS nie magicznie optymalizuje całego naszego kodu do wywołania ogona.
Więc jeśli CPS nie jest w stanie zrobić wszystkiego TCO, to czy istnieje transformacja specjalnie do bezpośredniej rekurencji, która może? Nie, ogólnie nie. Niektóre rekurencje są liniowe, ale niektóre nie. Rekurencje nieliniowe (np. Drzewa) muszą po prostu utrzymywać gdzieś zmienną liczbę stanów.
źródło