Si vous créez les opérations minimales d'un ordinateur générique à partir de zéro, "l'itération" vient d'abord comme un bloc de construction et consomme moins de ressources que la "récursivité", l'ergo est plus rapide.
Nous établirons une hiérarchie de concepts, en partant de zéro et en définissant en premier lieu les concepts de base et de base, puis nous construirons des concepts de second niveau avec ceux-ci, etc.
Premier concept: cellules mémoire, stockage, état . Pour faire quelque chose, vous avez besoin de lieux pour stocker les valeurs de résultats finales et intermédiaires. Supposons que nous ayons un tableau infini de cellules "entières", appelées Memory , M [0..Infinite].
Instructions: faites quelque chose - transformez une cellule, changez sa valeur. modifier l'état . Chaque instruction intéressante effectue une transformation. Les instructions de base sont les suivantes:
a) Définir et déplacer des cellules de mémoire
- stocker une valeur en mémoire, p.ex.: stocker 5 m [4]
- copier une valeur dans une autre position: par exemple: stocker m [4] m [8]
b) Logique et arithmétique
- et, ou, xor, non
- ajouter, sous, mul, div. par exemple, ajouter m [7] m [8]
Un agent d'exécution : un cœur dans un processeur moderne. Un "agent" est quelque chose qui peut exécuter des instructions. Un agent peut également être une personne suivant l'algorithme sur papier.
Ordre des étapes: une séquence d'instructions : c'est-à-dire: faites-le d'abord, faites-le ensuite, etc. Une séquence impérative d'instructions. Une seule expression de ligne est "une séquence impérative d'instructions". Si vous avez une expression avec un "ordre d'évaluation" spécifique, alors vous avez des étapes . Cela signifie que même une expression composée unique a des «étapes» implicites et a également une variable locale implicite (appelons-la «résultat»). par exemple:
4 + 3 * 2 - 5
(- (+ (* 3 2) 4 ) 5)
(sub (add (mul 3 2) 4 ) 5)
L'expression ci-dessus implique 3 étapes avec une variable "résultat" implicite.
// pseudocode
1. result = (mul 3 2)
2. result = (add 4 result)
3. result = (sub result 5)
Ainsi, même les expressions infixes, puisque vous avez un ordre d'évaluation spécifique, sont une séquence impérative d'instructions . L'expression implique une séquence d'opérations à effectuer dans un ordre spécifique, et parce qu'il y a des étapes , il y a aussi une variable intermédiaire "résultat" implicite.
Pointeur d'instruction : Si vous avez une séquence d'étapes, vous avez également un "pointeur d'instruction" implicite. Le pointeur d'instruction marque l'instruction suivante et avance après la lecture de l'instruction mais avant l'exécution de l'instruction.
Dans cette pseudo-machine informatique, le pointeur d'instructions fait partie de la mémoire . (Remarque: Normalement, le pointeur d'instruction sera un «registre spécial» dans un cœur de processeur, mais ici nous simplifierons les concepts et supposerons que toutes les données (registres inclus) font partie de la «mémoire»)
Saut - Une fois que vous avez un nombre ordonné d'étapes et un pointeur d'instructions , vous pouvez appliquer l' instruction " store " pour modifier la valeur du pointeur d'instructions lui-même. Nous appellerons cette utilisation spécifique de l' instruction store avec un nouveau nom: Jump . Nous utilisons un nouveau nom car il est plus facile de le considérer comme un nouveau concept. En modifiant le pointeur d'instruction, nous demandons à l'agent de «passer à l'étape x».
Itération infinie : en sautant en arrière, vous pouvez maintenant faire en sorte que l'agent "répète" un certain nombre d'étapes. À ce stade, nous avons une itération infinie.
1. mov 1000 m[30]
2. sub m[30] 1
3. jmp-to 2 // infinite loop
Conditionnel - Exécution conditionnelle des instructions. Avec la clause "conditionnelle", vous pouvez exécuter conditionnellement l'une des instructions en fonction de l'état actuel (qui peut être défini avec une instruction précédente).
Itération appropriée : Maintenant, avec la clause conditionnelle , nous pouvons échapper à la boucle infinie de l' instruction de saut en arrière . Nous avons maintenant une boucle conditionnelle , puis une itération appropriée
1. mov 1000 m[30]
2. sub m[30] 1
3. (if not-zero) jump 2 // jump only if the previous
// sub instruction did not result in 0
// this loop will be repeated 1000 times
// here we have proper ***iteration***, a conditional loop.
Attribution de noms : attribution de noms à un emplacement de mémoire spécifique contenant des données ou une étape . C'est juste une "commodité". Nous n'ajoutons aucune nouvelle instruction en ayant la capacité de définir des «noms» pour les emplacements de mémoire. «Nommer» n'est pas une instruction pour l'agent, c'est juste une commodité pour nous. La dénomination rend le code (à ce stade) plus facile à lire et plus facile à modifier.
#define counter m[30] // name a memory location
mov 1000 counter
loop: // name a instruction pointer location
sub counter 1
(if not-zero) jmp-to loop
Sous-programme à un niveau : supposons qu'il existe une série d'étapes que vous devez exécuter fréquemment. Vous pouvez stocker les étapes dans une position nommée en mémoire, puis passer à cette position lorsque vous devez les exécuter (appel). À la fin de la séquence, vous devrez revenir au point d' appel pour continuer l'exécution. Avec ce mécanisme, vous créez de nouvelles instructions (sous-routines) en composant des instructions de base.
Mise en œuvre: (aucun nouveau concept requis)
- Stocker le pointeur d'instruction actuel dans une position de mémoire prédéfinie
- sauter au sous-programme
- à la fin de la sous-routine, vous récupérez le pointeur d'instructions à partir de l'emplacement de mémoire prédéfini, en revenant effectivement à l'instruction suivante de l' appel d' origine
Problème avec l' implémentation à un niveau : vous ne pouvez pas appeler un autre sous-programme à partir d'un sous-programme. Si vous le faites, vous écraserez l'adresse de retour (variable globale), vous ne pourrez donc pas imbriquer d'appels.
Pour avoir une meilleure implémentation des sous-programmes: Vous avez besoin d'une pile
Pile : Vous définissez un espace mémoire pour fonctionner comme une "pile", vous pouvez "pousser" les valeurs sur la pile, et aussi "pop" la dernière valeur "poussée". Pour implémenter une pile, vous aurez besoin d'un pointeur de pile (similaire au pointeur d'instruction) qui pointe vers la «tête» réelle de la pile. Lorsque vous «poussez» une valeur, le pointeur de pile diminue et vous stockez la valeur. Lorsque vous «sautez», vous obtenez la valeur au pointeur de pile réel, puis le pointeur de pile est incrémenté.
Sous-programmes Maintenant que nous avons une pile, nous pouvons implémenter des sous-programmes appropriés permettant les appels imbriqués . L'implémentation est similaire, mais au lieu de stocker le pointeur d'instruction dans une position de mémoire prédéfinie, nous "poussons" la valeur de l'IP dans la pile . À la fin de la sous-routine, nous venons de «pop» la valeur de la pile, sautant effectivement à l'instruction après l' appel d' origine . Cette implémentation, ayant une «pile» permet d'appeler un sous-programme à partir d'un autre sous-programme. Avec cette implémentation, nous pouvons créer plusieurs niveaux d'abstraction lors de la définition de nouvelles instructions comme sous-programmes, en utilisant des instructions de base ou d'autres sous-programmes comme blocs de construction.
Récursivité : que se passe-t-il lorsqu'un sous-programme s'appelle lui-même?. C'est ce qu'on appelle la «récursivité».
Problème: L' écrasement des résultats intermédiaires locaux qu'un sous-programme peut stocker en mémoire. Puisque vous appelez / réutilisez les mêmes étapes, si le résultat intermédiaire est stocké dans des emplacements de mémoire prédéfinis (variables globales), il sera écrasé sur les appels imbriqués.
Solution: pour permettre la récursivité, les sous-programmes doivent stocker les résultats intermédiaires locaux dans la pile . Par conséquent, à chaque appel récursif (direct ou indirect), les résultats intermédiaires sont stockés dans différents emplacements de mémoire.
...
Enfin, notez que vous avez de nombreuses possibilités d'utiliser la récursivité. Vous avez des structures de données récursives partout, vous en examinez une maintenant: des parties du DOM supportant ce que vous lisez sont un RDS, une expression JSON est un RDS, le système de fichiers hiérarchique de votre ordinateur est un RDS, c'est-à-dire: vous avez un répertoire racine, contenant des fichiers et des répertoires, chaque répertoire contenant des fichiers et des répertoires, chacun de ces répertoires contenant des fichiers et des répertoires ...