Alice , 38 36 octets
Merci à Leo d'avoir économisé 2 octets.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Essayez-le en ligne!
Certainement pas optimal. Le flux de contrôle est assez élaboré et même si je suis assez satisfait du nombre d'octets enregistrés par rapport aux versions précédentes, j'ai le sentiment de trop compliquer les choses pour qu'il y ait une solution plus simple et plus courte.
Explication
Tout d'abord, je dois élaborer un peu sur la pile d'adresses de retour d'Alice (RAS). Comme beaucoup d'autres fungeoids, Alice a une commande pour sauter dans le code. Cependant, il a également des commandes pour retourner d'où vous venez, ce qui vous permet d'implémenter les sous-programmes très facilement. Bien sûr, étant un langage 2D, les sous-programmes n'existent vraiment que par convention. Rien ne vous empêche d'entrer ou de quitter un sous-programme par d'autres moyens qu'une commande de retour (ou à tout moment dans le sous-programme), et selon la façon dont vous utilisez le RAS, il pourrait ne pas y avoir de toute façon une hiérarchie de saut / retour propre.
En général, cela est implémenté en faisant en sorte que la commande jump j
envoie l'adresse IP actuelle au RAS avant de sauter. La commande de retour affiche k
ensuite une adresse du RAS et y saute. Si le RAS est vide, k
ne fait rien du tout.
Il existe également d'autres façons de manipuler le RAS. Deux d'entre elles sont pertinentes pour ce programme:
w
pousse l'adresse IP actuelle vers le RAS sans sauter n'importe où. Si vous répétez cette commande, vous pouvez écrire des boucles simples assez facilement, comme &w...k
je l'ai déjà fait dans les réponses précédentes.
J
est comme j
mais ne se souvient pas de l'adresse IP actuelle sur le RAS.
Il est également important de noter que le RAS ne stocke aucune information sur la direction de l'IP. Donc, revenir à une adresse avec k
préservera toujours la direction IP actuelle (et donc aussi si nous sommes en mode Cardinal ou Ordinal) quelle que soit la façon dont nous avons traversé le j
ou w
qui a poussé l'adresse IP en premier lieu.
Cela étant dit, commençons par examiner le sous-programme du programme ci-dessus:
01dt,t&w.2,+k
Ce sous-programme tire l'élément inférieur de la pile, n , vers le haut, puis calcule les nombres de Fibonacci F (n) et F (n + 1) (en les laissant en haut de la pile). Nous n'avons jamais besoin de F (n + 1) , mais il sera rejeté en dehors du sous-programme, en raison de la façon dont les &w...k
boucles interagissent avec le RAS (ce qui oblige ces boucles à se trouver à la fin d'un sous-programme). La raison pour laquelle nous prenons des éléments du bas au lieu du haut est que cela nous permet de traiter la pile plus comme une file d'attente, ce qui signifie que nous pouvons calculer tous les numéros de Fibonacci en une seule fois sans avoir à les stocker ailleurs.
Voici comment fonctionne ce sous-programme:
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.
La fin de la boucle est un peu délicate. Tant qu'il y a une copie de l'adresse «w» sur la pile, cela démarre l'itération suivante. Une fois ceux-ci épuisés, le résultat dépend de la façon dont le sous-programme a été invoqué. Si le sous-programme a été appelé avec 'j', le dernier 'k' y revient, donc la fin de la boucle se double du retour du sous-programme. Si le sous-programme a été appelé avec 'J', et qu'il y a toujours une adresse antérieure à la pile, nous sautons là. Cela signifie que si le sous-programme a été appelé dans une boucle externe elle-même, ce «k» revient au début de cette boucle externe . Si le sous-programme a été appelé avec «J» mais que le RAS est vide maintenant, alors ce «k» ne fait rien et l'IP continue simplement de se déplacer après la boucle. Nous utiliserons ces trois cas dans le programme.
Enfin, passons au programme lui-même.
/o....
\i@...
Ce ne sont que deux excursions rapides en mode Ordinal pour lire et imprimer des entiers décimaux.
Après le i
, il y en a un w
qui se souvient de la position actuelle avant de passer dans le sous-programme, en raison du second /
. Cette première invocation du sous-programme calcule F(n)
et F(n+1)
sur l'entrée n
. Ensuite, nous sautons ici, mais nous nous déplaçons vers l'est maintenant, donc le reste des opérateurs de programme en mode Cardinal. Le programme principal ressemble à ceci:
;B1dt&w;31J;d&+
^^^
Ici, 31J
c'est un autre appel à la sous-routine et calcule donc un nombre 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.