アリス、38 36バイト
2バイトを節約してくれたLeoに感謝します。
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
オンラインでお試しください!
ほぼ確実に最適ではありません。制御フローはかなり複雑で、以前のバージョンで保存したバイト数に非常に満足していますが、もっと単純で、短いソリューションます。
説明
最初に、Aliceのリターンアドレススタック(RAS)について少し詳しく説明する必要があります。他の多くのFungeoidと同様に、Aliceにはコード内をジャンプするコマンドがあります。ただし、元の場所に戻るコマンドもあるため、サブルーチンを非常に便利に実装できます。もちろん、これは2D言語なので、サブルーチンは実際には慣例によってのみ存在します。returnコマンド以外の方法(またはサブルーチン内の任意の時点)でサブルーチンを開始または終了することを妨げるものは何もありません。また、RASの使用方法によっては、とにかくきれいなジャンプ/リターン階層が存在しない場合があります。
一般に、これは、ジャンプコマンドを使用して、ジャンプj
する前に現在のIPアドレスをRASにプッシュすることによって実装されます。次に、returnコマンドk
はRASのアドレスをポップし、そこにジャンプします。RASが空の場合、k
何もしません。
RASを操作する他の方法もあります。これらの2つは、このプログラムに関連しています。
w
どこにもジャンプすることなく、現在のIPアドレスをRASにプッシュします。このコマンドを繰り返すと、単純なループをとして非常に便利に書くことができ&w...k
ます。
J
のようなものですj
が、RASの現在のIPアドレスを覚えていません。
また、RAS はIPの方向に関する情報を保存しないことに注意することも重要です。アドレスに戻るので、k
常に保持されます、現在にかかわらず、我々が通過したかの(私たちは枢機卿または序モードにいるかどうかもそのためと)IP方向をj
かw
、最初にIPアドレスプッシュした。
それが終わったら、上記のプログラムのサブルーチンを調べてみましょう。
01dt,t&w.2,+k
このサブルーチンは、スタックの一番下の要素nを一番上に引き出し、フィボナッチ数F(n)とF(n + 1)を計算します(スタックの一番上に残します)。F(n + 1)は必要ありませんが、&w...k
ループがRASと対話する方法により、サブルーチンの外で破棄されます(これらのループはサブルーチンの最後にある必要があります)。上部ではなく下部から要素を取得する理由は、スタックをキューのように扱うことができるためです。つまり、フィボナッチ数を他の場所に保存せずに一度に計算できます。
このサブルーチンの仕組みは次のとおりです。
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.
ループの終わりには少し注意が必要です。スタックに「w」アドレスのコピーがある限り、これは次の反復を開始します。それらが使い果たされると、結果はサブルーチンの起動方法に依存します。サブルーチンが「j」で呼び出された場合、最後の「k」がそこに戻るため、ループの終わりはサブルーチンの戻りとして2倍になります。サブルーチンが 'J'で呼び出されたが、スタックの以前のアドレスがまだある場合は、そこにジャンプします。つまり、サブルーチンが外部ループ自体で呼び出された場合、この「k」はその外部ループの先頭に戻ります。サブルーチンが「J」で呼び出されたが、現在RASが空の場合、この「k」は何もせず、IPはループの後に移動し続けます。これらの3つのケースすべてをプログラムで使用します。
最後に、プログラム自体について説明します。
/o....
\i@...
これらは、10進整数の読み取りと印刷を行うための序数モードへの2つの簡単なエクスカーションです。
の後に、サブルーチンに渡される前に現在の位置を記憶i
するがw
あります/
。サブルーチンのこの最初の呼び出しは、計算F(n)
とF(n+1)
入力でn
。その後、ここに戻りますが、現在は東に移動しているため、残りのプログラム演算子はCardinalモードになります。メインプログラムは次のようになります。
;B1dt&w;31J;d&+
^^^
ここ31J
に、サブルーチンへの別の呼び出しがあり、したがってフィボナッチ数を計算します。
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.