Alice , 38 36 bytes
Gracias a Leo por guardar 2 bytes.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Pruébalo en línea!
Casi seguro que no es óptimo. El flujo de control es bastante elaborado y, si bien estoy bastante contento con la cantidad de bytes que se guardaron en las versiones anteriores, tengo la sensación de que estoy complicando demasiado las cosas de que podría haber una solución más simple y más corta.
Explicación
Primero, necesito elaborar un poco sobre la pila de direcciones de retorno de Alice (RAS). Al igual que muchos otros fungeoides, Alice tiene un comando para saltar en el código. Sin embargo, también tiene comandos para regresar al lugar de donde vino, lo que le permite implementar subrutinas de manera bastante conveniente. Por supuesto, al tratarse de un lenguaje 2D, las subrutinas solo existen por convención. No hay nada que le impida ingresar o salir de una subrutina a través de otros medios que no sean un comando de retorno (o en cualquier punto de la subrutina), y dependiendo de cómo use el RAS, puede que no haya una jerarquía de salto / retorno limpia de todos modos.
En general, esto se implementa haciendo que el comando de salto j
empuje la dirección IP actual al RAS antes de saltar. El comando de retorno k
luego muestra una dirección del RAS y salta allí. Si el RAS está vacío,k
no hace nada en absoluto.
También hay otras formas de manipular el RAS. Dos de estos son relevantes para este programa:
w
empuja la dirección IP actual al RAS sin saltar a ningún lado. Si repite este comando, puede escribir bucles simples de manera bastante conveniente &w...k
, como ya lo hice en respuestas anteriores.
J
es como j
pero no recuerda la dirección IP actual en el RAS.
También es importante tener en cuenta que el RAS no almacena información sobre la dirección de la IP. Por lo tanto, volver a una dirección con k
siempre conservará la dirección IP actual (y, por lo tanto, también si estamos en modo Cardinal u Ordinal) independientemente de cómo pasamos por el j
ow
de que empujó la dirección IP en el primer lugar.
Una vez dicho esto, comencemos analizando la subrutina en el programa anterior:
01dt,t&w.2,+k
Esta subrutina tira del elemento inferior de la pila, n , hacia la parte superior y luego calcula los números de Fibonacci F (n) y F (n + 1) (dejándolos en la parte superior de la pila). Nunca necesitamos F (n + 1) , pero se descartará fuera de la subrutina, debido a cómo los &w...k
bucles interactúan con el RAS (que requiere que estos bucles estén al final de una subrutina). La razón por la que estamos tomando elementos de la parte inferior en lugar de la parte superior es que esto nos permite tratar la pila más como una cola, lo que significa que podemos calcular todos los números de Fibonacci de una sola vez sin tener que almacenarlos en otro lugar.
Así es como funciona esta subrutina:
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.
El final del ciclo es un poco complicado. Mientras haya una copia de la dirección 'w' en la pila, esto iniciará la próxima iteración. Una vez que se agotan, el resultado depende de cómo se invocó la subrutina. Si se llamó a la subrutina con 'j', la última 'k' regresa allí, por lo que el final del ciclo se duplica como el retorno de la subrutina. Si se llamó a la subrutina con 'J', y todavía hay una dirección anterior en la pila, saltamos allí. Esto significa que si la subrutina se llamó en un bucle externo en sí, esta 'k' regresa al comienzo de ese bucle externo . Si se llamó a la subrutina con 'J' pero el RAS está vacío ahora, entonces esta 'k' no hace nada y la IP simplemente sigue moviéndose después del ciclo. Usaremos los tres casos en el programa.
Finalmente, al programa en sí.
/o....
\i@...
Estas son solo dos excursiones rápidas al modo Ordinal para leer e imprimir enteros decimales.
Después de la i
, hay una w
que recuerda la posición actual antes de pasar a la subrutina, debido a la segunda /
. Esta primera invocación de la subrutina se calcula F(n)
y F(n+1)
en la entrada n
. Luego saltamos de regreso aquí, pero ahora nos estamos moviendo hacia el este, por lo que el resto de los operadores del programa están en modo Cardinal. El programa principal se ve así:
;B1dt&w;31J;d&+
^^^
Aquí, 31J
hay otra llamada a la subrutina y, por lo tanto, calcula un 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.