Alice , 38 36 bytes
Agradecemos a Leo por salvar 2 bytes.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Experimente online!
Quase certamente não é o ideal. O fluxo de controle é bastante elaborado e, embora eu esteja feliz com a quantidade de bytes que foram salvos nas versões anteriores, sinto que estou complicando demais as coisas, que podem ser mais simples e solução mais mais curta.
Explicação
Primeiro, preciso elaborar um pouco a pilha de endereços de retorno (RAS) de Alice. Como muitos outros fungeóides, Alice tem um comando para pular o código. No entanto, também possui comandos para retornar de onde você veio, o que permite implementar sub-rotinas de maneira bastante conveniente. Obviamente, sendo uma linguagem 2D, as sub-rotinas realmente existem apenas por convenção. Não há nada que impeça você de entrar ou sair de uma sub-rotina por outros meios além de um comando de retorno (ou em qualquer ponto da sub-rotina) e, dependendo de como você usa o RAS, talvez não exista uma hierarquia de salto / retorno limpa.
Em geral, isso é implementado com o comando jump j
enviar o endereço IP atual ao RAS antes de saltar. O comando de retorno k
aparece um endereço do RAS e salta para lá. Se o RAS estiver vazio, k
não fará nada.
Há também outras maneiras de manipular o RAS. Dois deles são relevantes para este programa:
w
envia o endereço IP atual para o RAS sem ir para qualquer lugar. Se você repetir esse comando, poderá escrever loops simples de maneira bastante conveniente,&w...k
, já fiz em respostas anteriores.
J
é como, j
mas não se lembra do endereço IP atual no RAS.
Também é importante observar que o RAS não armazena informações sobre a direção do IP. Portanto, retornar a um endereço com k
sempre preservará a direção atual do IP (e, portanto, também se estamos no modo Cardinal ou Ordinal), independentemente de como passamos pelo j
ouw
que empurrou o endereço IP em primeiro lugar.
Com isso, vamos começar examinando a sub-rotina no programa acima:
01dt,t&w.2,+k
Essa sub-rotina puxa o elemento inferior da pilha, n , para o topo e depois calcula os números de Fibonacci F (n) e F (n + 1) (deixando-os no topo da pilha). Nunca precisamos de F (n + 1) , mas ele será descartado fora da sub-rotina, devido à forma como os &w...k
loops interagem com o RAS (que tipo de requer que esses loops estejam no final de uma sub-rotina). A razão pela qual estamos pegando elementos da parte inferior em vez da parte superior é que isso nos permite tratar a pilha mais como uma fila, o que significa que podemos calcular todos os números de Fibonacci de uma só vez sem precisar armazená-los em outro lugar.
Aqui está como essa sub-rotina funciona:
Stack
01 Push 0 and 1, to initialise Fibonacci sequence. [n ... 0 1]
dt, Pull bottom element n to top. [... 0 1 n]
t&w Run this loop n times... [... F(i-2) F(i-1)]
. Duplicate F(i-1). [... F(i-2) F(i-1) F(i-1)]
2, Pull up F(i-2). [... F(i-1) F(i-1) F(i-2)]
+ Add them together to get F(i). [... F(i-1) F(i)]
k End of loop.
O final do ciclo é um pouco complicado. Enquanto houver uma cópia do endereço 'w' na pilha, isso inicia a próxima iteração. Uma vez esgotados, o resultado depende de como a sub-rotina foi invocada. Se a sub-rotina foi chamada com 'j', o último 'k' retorna lá, então o final do loop é duplicado como o retorno da sub-rotina. Se a sub-rotina foi chamada com 'J' e ainda há um endereço anterior na pilha, pulamos para lá. Isso significa que se a sub-rotina foi chamada no próprio loop externo, esse 'k' retorna ao início desse loop externo . Se a sub-rotina foi chamada com 'J', mas o RAS está vazio agora, esse 'k' não faz nada e o IP simplesmente continua se movendo após o loop. Usaremos todos os três casos no programa.
Finalmente, para o próprio programa.
/o....
\i@...
Estas são apenas duas excursões rápidas ao modo Ordinal para ler e imprimir números inteiros decimais.
Após o i
, existe um w
que lembra a posição atual antes de passar para a sub-rotina, devido à segunda /
. Essa primeira chamada da sub-rotina calcula F(n)
e F(n+1)
na entrada n
. Depois, voltamos para cá, mas estamos indo para o leste agora, então o restante dos operadores do programa está no modo Cardinal. O programa principal fica assim:
;B1dt&w;31J;d&+
^^^
Aqui 31J
está outra chamada para a sub-rotina e, portanto, calcula um número de Fibonacci.
Stack
[F(n) F(n+1)]
; Discard F(n+1). [F(n)]
B Push all divisors of F(n). [d_1 d_2 ... d_p]
1 Push 1. This value is arbitrary. [d_1 d_2 ... d_p 1]
The reason we need it is due to
the fact that we don't want to run
any code after our nested loops, so
the upcoming outer loop over all
divisors will *start* with ';' to
discard F(d+1). But on the first
iteration we haven't called the
subroutine yet, so we need some
dummy value we can discard.
dt&w Run this loop once for each element [d_1 d_2 ... d_p 1]
in the stack. Note that this is once OR
more than we have divisors. But since [d_i d_(i+1) ... F(d_(i-1)) F(d_(i-1)+1)]
we're treating the stack as a queue,
the last iteration will process the
first divisor for a second time.
Luckily, the first divisor is always
1 and F(1) = 1, so it doesn't matter
how often we process this one.
; Discard the dummy value on the [d_1 d_2 ... d_p]
first iteration and F(d+1) of OR
the previous divisor on subsequent [d_i d_(i+1) ... F(d_(i-1))]
iterations.
31J Call the subroutine without pushing [d_(i+1) ... F(d_i) F(d_i+1)]
the current address on the RAS.
Thereby, this doubles as our outer
loop end. As long as there's an
address left from the 'w', the end
of the subroutine will jump there
and start another iteration for the
next divisor. Once that's done, the
'k' at the end of the subroutine will
simply do nothing and we'll continue
after it.
; Discard the final F(d_i+1).
d&+ Get the stack depth D and add the top [final result]
D+2 values. Of course that's two more
than we have divisors, but the stack is
implicitly padded with zeros, so that
doesn't matter.