Se você criar as operações mínimas de um computador genérico a partir do zero, a "Iteração" vem primeiro como um bloco de construção e consome menos recursos do que a "recursão", e a ergo é mais rápida.
Estabeleceremos uma hierarquia de conceitos, começando do zero e definindo em primeiro lugar os conceitos básicos e básicos, depois construindo conceitos de segundo nível com esses e assim por diante.
Primeiro conceito: células de memória, armazenamento, estado . Para fazer algo, você precisa de lugares para armazenar valores de resultado finais e intermediários. Vamos supor que temos uma matriz infinita de células "inteiras", chamadas Memória , M [0..Infinito].
Instruções: faça alguma coisa - transforme uma célula, altere seu valor. alterar estado . Toda instrução interessante realiza uma transformação. As instruções básicas são:
a) Definir e mover células de memória
- armazena um valor na memória, por exemplo: armazena 5 m [4]
- copie um valor para outra posição: por exemplo: armazenar m [4] m [8]
b) Lógica e aritmética
- e, ou, xor, não
- adicionar, sub, mul, div. por exemplo, adicione m [7] m [8]
Um agente de execução : um núcleo em uma CPU moderna. Um "agente" é algo que pode executar instruções. Um agente também pode ser uma pessoa seguindo o algoritmo no papel.
Ordem dos passos: uma sequência de instruções : ie: faça isso primeiro, faça depois, etc. Uma sequência imperativa de instruções. Mesmo as expressões de uma linha são "uma sequência imperativa de instruções". Se você tiver uma expressão com uma "ordem de avaliação" específica, terá etapas . Isso significa que mesmo uma única expressão composta possui "etapas" implícitas e também possui uma variável local implícita (vamos chamá-la de "resultado"). por exemplo:
4 + 3 * 2 - 5
(- (+ (* 3 2) 4 ) 5)
(sub (add (mul 3 2) 4 ) 5)
A expressão acima implica 3 etapas com uma variável implícita "resultado".
// pseudocode
1. result = (mul 3 2)
2. result = (add 4 result)
3. result = (sub result 5)
Portanto, mesmo expressões infix, como você tem uma ordem específica de avaliação, são uma sequência imperativa de instruções . A expressão implica uma sequência de operações a serem feitas em uma ordem específica e, como existem etapas , também há uma variável intermediária implícita "resultado".
Ponteiro de instrução : Se você tiver uma sequência de etapas, também terá um "ponteiro de instrução" implícito. O ponteiro da instrução marca a próxima instrução e avança após a leitura da instrução, mas antes da execução da instrução.
Nesta pseudo-máquina de computação, o ponteiro de instruções faz parte da memória . (Nota: normalmente o ponteiro de instruções será um "registro especial" no núcleo da CPU, mas aqui simplificaremos os conceitos e assumiremos que todos os dados (registros incluídos) fazem parte de "Memória")
Pular - Depois de ter um número ordenado de etapas e um ponteiro de instruções , você pode aplicar a instrução " armazenar " para alterar o valor do ponteiro de instruções. Chamaremos esse uso específico de instrução de loja com um novo nome: Jump . Usamos um novo nome porque é mais fácil pensar nele como um novo conceito. Alterando o ponteiro de instruções, instruímos o agente a "ir para a etapa x".
Iteração Infinita : Ao voltar, agora você pode fazer o agente "repetir" um certo número de etapas. Neste ponto, temos iteração infinita.
1. mov 1000 m[30]
2. sub m[30] 1
3. jmp-to 2 // infinite loop
Condicional - Execução condicional de instruções. Com a cláusula "condicional", você pode executar condicionalmente uma das várias instruções com base no estado atual (que pode ser definido com uma instrução anterior).
Iteração adequada : Agora, com a cláusula condicional , podemos escapar do loop infinito da instrução de retorno . Agora temos um loop condicional e, em seguida, iteração adequada
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.
Nomeação : atribuir nomes a um local específico da memória contendo dados ou mantendo uma etapa . Esta é apenas uma "conveniência" de ter. Não adicionamos novas instruções por ter a capacidade de definir "nomes" para locais de memória. "Nomear" não é uma instrução para o agente, é apenas uma conveniência para nós. A nomeação torna o código (neste ponto) mais fácil de ler e mais fácil de alterar.
#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
Sub-rotina de um nível : suponha que haja uma série de etapas que você precisa executar com freqüência. Você pode armazenar as etapas em uma posição nomeada na memória e, em seguida, pular para essa posição quando precisar executá-las (chamada). No final da sequência, você precisará retornar ao ponto de chamada para continuar a execução. Com esse mecanismo, você cria novas instruções (sub-rotinas) compondo as instruções principais.
Implementação: (não são necessários novos conceitos)
- Armazene o ponteiro de instruções atual em uma posição de memória predefinida
- pule para a sub-rotina
- no final da subrotina, você recupera o ponteiro de instruções do local predefinido da memória, retornando efetivamente à instrução a seguir da chamada original
Problema com a implementação em um nível : Você não pode chamar outra sub-rotina a partir de uma sub-rotina. Se o fizer, substituirá o endereço de retorno (variável global), para não aninhar chamadas.
Para ter uma melhor implementação de sub-rotinas: Você precisa de uma PILHA
Pilha : você define um espaço de memória para funcionar como uma "pilha", pode "empurrar" os valores na pilha e também "pop" o último valor "empurrado". Para implementar uma pilha, você precisará de um ponteiro de pilha (semelhante ao ponteiro de instruções) que aponta para o "cabeçalho" real da pilha. Quando você pressiona um valor, o ponteiro da pilha diminui e você armazena o valor. Quando você "pop", você obtém o valor no ponteiro da pilha real e, em seguida, o ponteiro da pilha é incrementado.
Sub-rotinas Agora que temos uma pilha , podemos implementar sub-rotinas adequadas, permitindo chamadas aninhadas . A implementação é semelhante, mas em vez de armazenar o Ponteiro de Instruções em uma posição de memória predefinida, "pressionamos" o valor do IP na pilha . No final da sub-rotina, apenas "pop" o valor da pilha, retornando efetivamente à instrução após a chamada original . Essa implementação, ter uma “pilha” permite chamar uma sub-rotina de outra sub-rotina. Com essa implementação, podemos criar vários níveis de abstração ao definir novas instruções como sub-rotinas, usando instruções principais ou outras sub-rotinas como blocos de construção.
Recursão : o que acontece quando uma sub-rotina se chama? Isso é chamado de "recursão".
Problema: Substituindo os resultados intermediários locais que uma sub-rotina pode estar armazenando na memória. Como você está chamando / reutilizando as mesmas etapas, se o resultado intermediário for armazenado em locais de memória predefinidos (variáveis globais), eles serão substituídos nas chamadas aninhadas.
Solução: Para permitir recursão, as sub-rotinas devem armazenar resultados intermediários locais na pilha ; portanto, a cada chamada recursiva (direta ou indireta), os resultados intermediários são armazenados em diferentes locais da memória.
...
Por fim, observe que você tem muitas oportunidades de usar recursão. Você tem estruturas de dados recursivas em todos os lugares, agora está vendo uma: partes do DOM que suportam o que você está lendo são um RDS, uma expressão JSON é um RDS, o sistema de arquivos hierárquico no seu computador é um RDS, ou seja: você um diretório raiz, contendo arquivos e diretórios, todo diretório que contém arquivos e diretórios, cada um desses diretórios que contém arquivos e diretórios ...