Alice , 38 36 Bytes
Danke an Leo für das Speichern von 2 Bytes.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Probieren Sie es online!
Mit ziemlicher Sicherheit nicht optimal. Der Kontrollfluss ist ziemlich kompliziert und obwohl ich ziemlich zufrieden bin mit der Anzahl der Bytes, die gegenüber früheren Versionen gespeichert wurden, habe ich das Gefühl, dass ich Dinge, die einfacher sein könnten, überkompliziere und kürzere Lösung .
Erläuterung
Zuerst muss ich etwas über Alices Return Address Stack (RAS) herausfinden. Wie viele andere Fungeoids hat Alice den Befehl, im Code herumzuspringen. Es gibt jedoch auch Befehle, mit denen Sie zu Ihrem Ursprungsort zurückkehren können, sodass Sie Subroutinen ganz bequem implementieren können. Da es sich um eine 2D-Sprache handelt, existieren Unterprogramme natürlich nur nach Konvention. Es gibt nichts, was Sie daran hindert, eine Unterroutine mit anderen Mitteln als einem Rückkehrbefehl (oder an einem beliebigen Punkt in der Unterroutine) zu betreten oder zu verlassen, und je nachdem, wie Sie den RAS verwenden, gibt es möglicherweise ohnehin keine saubere Sprung- / Rückkehrhierarchie.
Im Allgemeinen wird dies implementiert, indem der Sprungbefehl j
die aktuelle IP-Adresse zum RAS überträgt, bevor der Sprung ausgeführt wird. Der Rücksprungbefehl gibt k
dann eine Adresse des RAS aus und springt dorthin. Wenn der RAS leer ist, k
geschieht gar nichts.
Es gibt auch andere Möglichkeiten, den RAS zu manipulieren. Zwei davon sind für dieses Programm relevant:
w
schiebt die aktuelle IP-Adresse zum RAS, ohne irgendwo zu springen. Wenn Sie diesen Befehl wiederholen, können Sie ganz bequem einfache Schleifen schreiben &w...k
, wie ich es bereits in früheren Antworten getan habe.
J
ist wie j
, merkt sich aber nicht die aktuelle IP-Adresse auf dem RAS.
Es ist auch wichtig zu beachten, dass der RAS keine Informationen über die Richtung der IP speichert . Wenn Sie also zu einer Adresse mit zurückkehren, k
wird immer die aktuelle IP-Richtung beibehalten (und daher auch, ob Sie sich im Kardinal- oder Ordinal-Modus befinden), unabhängig davon, wie wir die IP-Adresse durchlaufen haben j
oder durch w
welche die IP-Adresse übertragen wurde.
Nachdem dies erledigt ist, schauen wir uns zunächst die Subroutine im obigen Programm an:
01dt,t&w.2,+k
Diese Unterroutine zieht das untere Element des Stapels n nach oben und berechnet dann die Fibonacci-Zahlen F (n) und F (n + 1) (wobei sie oben auf dem Stapel verbleiben). Wir brauchen niemals F (n + 1) , aber es wird außerhalb der Unterroutine verworfen, da &w...k
Schleifen mit dem RAS interagieren (was erfordert, dass diese Schleifen am Ende sind) einer Unterroutine sein müssen). Der Grund, warum wir Elemente von unten anstelle von oben verwenden, besteht darin, dass wir den Stapel eher wie eine Warteschlange behandeln. Das heißt, wir können alle Fibonacci-Zahlen auf einmal berechnen, ohne sie an einer anderen Stelle speichern zu müssen.
So funktioniert dieses Unterprogramm:
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.
Das Ende der Schleife ist etwas knifflig. Solange sich eine Kopie der 'w'-Adresse auf dem Stapel befindet, wird die nächste Iteration gestartet. Sobald diese erschöpft sind, hängt das Ergebnis davon ab, wie die Unterroutine aufgerufen wurde. Wenn das Unterprogramm mit 'j' aufgerufen wurde, kehrt das letzte 'k' dorthin zurück, sodass das Schleifenende als Rückkehr des Unterprogramms gilt. Wenn das Unterprogramm mit 'J' aufgerufen wurde und es noch eine Adresse von früher auf dem Stapel gibt, springen wir dorthin. Dies bedeutet, wenn das Unterprogramm in einer äußeren Schleife selbst aufgerufen wurde, kehrt dieses 'k' zum Anfang dieser äußeren Schleife zurück. Wurde das Unterprogramm mit 'J' aufgerufen, aber der RAS ist jetzt leer, dann macht dieses 'k' nichts und die IP bewegt sich einfach weiter nach der Schleife. Wir werden alle drei dieser Fälle im Programm verwenden.
Zum Schluss zum Programm selbst.
/o....
\i@...
Dies sind nur zwei schnelle Exkursionen in den Ordnungsmodus, um Dezimalzahlen zu lesen und zu drucken.
Nach dem i
gibt es einen, w
der sich aufgrund der Sekunde die aktuelle Position merkt, bevor er in das Unterprogramm übergeht /
. Dieser erste Aufruf des Unterprogramms berechnet F(n)
und übergibt F(n+1)
die Eingabe n
. Danach springen wir zurück, aber wir ziehen jetzt nach Osten, also der Rest der Programmoperatoren im Kardinalmodus. Das Hauptprogramm sieht folgendermaßen aus:
;B1dt&w;31J;d&+
^^^
Hier 31J
ist ein weiterer Aufruf des Unterprogramms und berechnet daher eine Fibonacci-Nummer.
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.