Alice , 38 36 byte
Grazie a Leo per aver salvato 2 byte.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Provalo online!
Quasi certamente non ottimale. Il flusso di controllo è abbastanza elaborato e mentre sono abbastanza contento di quanti byte sono stati salvati rispetto alle versioni precedenti, ho la sensazione di complicare troppo le cose che potrebbero esserci soluzione più e più breve.
Spiegazione
Innanzitutto, devo elaborare un po 'lo stack degli indirizzi di ritorno (RAS) di Alice. Come molti altri fungeoid, Alice ha il comando di saltare nel codice. Tuttavia, ha anche i comandi per tornare da dove vieni, il che ti consente di implementare le subroutine abbastanza comodamente. Naturalmente, trattandosi di un linguaggio 2D, le subroutine esistono davvero solo per convenzione. Non c'è nulla che ti impedisca di entrare o uscire da una subroutine con mezzi diversi da un comando di ritorno (o in qualsiasi punto della subroutine) e, a seconda di come usi il RAS, potrebbe non esserci comunque una gerarchia pulita di salto / ritorno.
In generale, questo viene implementato facendo in modo che il comando jump j
spinga l'attuale indirizzo IP sul RAS prima di saltare. Il comando return k
quindi visualizza un indirizzo del RAS e salta lì. Se il RAS è vuoto, k
non fa nulla.
Esistono anche altri modi per manipolare il RAS. Due di questi sono rilevanti per questo programma:
w
invia l'attuale indirizzo IP al RAS senza saltare da nessuna parte. Se ripeti questo comando puoi scrivere semplici loop abbastanza comodamente come &w...k
, cosa che ho già fatto nelle risposte precedenti.
J
è come j
ma non ricorda l'attuale indirizzo IP sul RAS.
È anche importante notare che il RAS non memorizza informazioni sulla direzione dell'IP. Quindi il ritorno a un indirizzo con k
manterrà sempre la direzione IP corrente (e quindi anche se siamo in modalità Cardinale o Ordinale) indipendentemente da come abbiamo attraversato j
o w
che ha spinto l'indirizzo IP in primo luogo.
Detto questo, iniziamo esaminando la subroutine nel programma sopra:
01dt,t&w.2,+k
Questa subroutine tira in alto l'elemento inferiore della pila, n , quindi calcola i numeri di Fibonacci F (n) e F (n + 1) (lasciandoli in cima alla pila). Non abbiamo mai bisogno di F (n + 1) , ma verrà scartato al di fuori della subroutine, a causa del modo in cui i &w...k
loop interagiscono con il RAS (che tipo di richiede che questi loop siano alla fine di una subroutine). Il motivo per cui stiamo prendendo gli elementi dal basso invece che dall'alto è che questo ci consente di trattare lo stack più come una coda, il che significa che possiamo calcolare tutti i numeri di Fibonacci in una volta sola senza doverli memorizzare altrove.
Ecco come funziona questa subroutine:
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 fine del ciclo è un po 'complicata. Finché c'è una copia dell'indirizzo "w" nello stack, questo avvia la successiva iterazione. Una volta esauriti, il risultato dipende da come è stata invocata la subroutine. Se la subroutine è stata chiamata con 'j', l'ultima 'k' vi ritorna, quindi la fine del ciclo raddoppia come ritorno della subroutine. Se la subroutine è stata chiamata con 'J', e c'è ancora un indirizzo precedente nello stack, saltiamo lì. Ciò significa che se la subroutine è stata chiamata in un loop esterno stesso, questa 'k' ritorna all'inizio di quel loop esterno . Se la subroutine è stata chiamata con 'J' ma ora il RAS è vuoto, allora 'k' non fa nulla e l'IP semplicemente continua a muoversi dopo il loop. Useremo tutti e tre questi casi nel programma.
Infine, al programma stesso.
/o....
\i@...
Queste sono solo due brevi escursioni in modalità Ordinale per leggere e stampare numeri decimali.
Dopo il i
, c'è un w
che ricorda la posizione corrente prima di passare alla subroutine, a causa del secondo /
. Questa prima invocazione della subroutine viene calcolata F(n)
e F(n+1)
sull'input n
. Successivamente torniamo indietro qui, ma ora ci stiamo spostando verso est, quindi il resto degli operatori del programma in modalità Cardinale. Il programma principale è simile al seguente:
;B1dt&w;31J;d&+
^^^
Ecco 31J
un'altra chiamata alla subroutine e quindi calcola un numero di 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.