Il semble que j'ai trouvé un moyen générique de convertir toute procédure récursive en récursion de queue:
- Définissez une sous-procédure d'assistance avec un paramètre "résultat" supplémentaire.
- Appliquez ce qui serait appliqué à la valeur de retour de la procédure à ce paramètre.
- Appelez cette procédure d'assistance pour commencer. La valeur initiale du paramètre "result" est la valeur du point de sortie du processus récursif, de sorte que le processus itératif résultant commence à partir de l'endroit où le processus récursif commence à diminuer.
Par exemple, voici la procédure récursive d'origine à convertir ( exercice 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))))
Voici la procédure convertie et récursive de queue ( exercice 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))
Quelqu'un peut-il prouver ou infirmer cela?
algorithms
logic
recursion
lisp
nalzok
la source
la source
b
d'être une puissance de 2 montre que le réglage initialproduct
à 0 n'est pas tout à fait correct; mais le changer en 1 ne fonctionne pas quandb
c'est étrange. Peut-être avez-vous besoin de 2 paramètres d'accumulateur différents?Réponses:
Votre description de votre algorithme est vraiment trop vague pour l'évaluer à ce stade. Mais, voici quelques éléments à considérer.
CPS
En fait, il existe un moyen de transformer n'importe quel code en un formulaire qui n'utilise que des appels de queue. Il s'agit de la transformation CPS. CPS ( Continuation-Passing Style ) est une forme d'expression de code en passant à chaque fonction une continuation. Une continuation est une notion abstraite représentant "le reste d'un calcul". Dans le code exprimé sous forme CPS, la façon naturelle de réifier une continuation est comme une fonction qui accepte une valeur. Dans CPS, au lieu d'une fonction renvoyant une valeur, il applique à la place la fonction représentant la continuation en cours au "retourné" par la fonction.
Par exemple, considérez la fonction suivante:
Cela pourrait être exprimé en CPS comme suit:
C'est moche et souvent lent, mais cela présente certains avantages:
TCO
Il me semble que la seule raison de se préoccuper de la récursivité de queue (ou des appels de queue en général) est à des fins d'optimisation de l'appel de queue (TCO). Donc, je pense qu'une meilleure question à poser est "ma transformation donne-t-elle un code qui soit optimisé par l'appel de queue?".
Si nous considérons une fois de plus CPS, l'une de ses caractéristiques est que le code exprimé en CPS consiste uniquement en appels de queue. Puisque tout est un appel de queue, nous n'avons pas besoin d'enregistrer un point de retour dans la pile. Donc, tout le code sous forme CPS doit être optimisé pour les appels de queue, non?
Enfin, pas tout à fait. Vous voyez, alors qu'il peut sembler que nous avons éliminé la pile, tout ce que nous avons fait est simplement de changer la façon dont nous la représentons. La pile fait maintenant partie de la fermeture représentant une continuation. Donc, CPS ne fait pas comme par magie tous nos appels de code optimisés.
Donc, si CPS ne peut pas tout faire TCO, y a-t-il une transformation spécifiquement pour la récursivité directe qui le peut? Non, pas en général. Certaines récursions sont linéaires, mais d'autres ne le sont pas. Les récursions non linéaires (par exemple, arborescentes) doivent simplement maintenir un état variable quelque part.
la source