爱丽丝,38 36字节
感谢Leo节省2个字节。
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
在线尝试!
几乎可以肯定不是最佳的。控制流程相当复杂,尽管我对以前的版本中保存了多少字节感到非常满意,但我觉得我过于复杂了,可能会有一个更简单,更短的解决方案。
说明
首先,我需要详细介绍Alice的返回地址堆栈(RAS)。像许多其他fungeoid一样,Alice也有一个在代码中跳转的命令。但是,它也有命令可以返回您的来源,这使您可以非常方便地实现子例程。当然,这是一种2D语言,子例程实际上仅按约定存在。除了返回命令(或子例程中的任何点)以外,没有什么可以阻止您通过其他方式进入或离开子例程,并且根据您使用RAS的方式,可能始终没有干净的跳转/返回层次结构。
通常,这是通过使跳转命令在跳转j
之前将当前IP地址推送到RAS来实现的。然后,return命令k
会弹出RAS的地址并跳转到该地址。如果RAS为空,k
则什么也不做。
还有其他方法可以操作RAS。其中两个与此程序有关:
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”返回那里,因此循环结束是子例程返回的两倍。如果用“ J”调用该子例程,并且在堆栈的前面还有一个地址,我们跳转到那里。这意味着,如果子例程本身是在外循环中调用的,则此“ k”将返回该外循环的开头。如果用“ J”调用该子例程,但是RAS现在为空,则此“ k”不执行任何操作,并且IP在循环之后只是继续移动。我们将在程序中使用所有这三种情况。
最后,进入程序本身。
/o....
\i@...
这只是进入序数模式的两个快速操作,以读取和打印十进制整数。
之后的i
,由于存在第二个,所以有一个w
在进入子例程之前记住当前位置/
。这个子程序的第一次调用计算F(n)
和F(n+1)
输入n
。之后,我们跳回此处,但现在我们要向东移动,因此其余程序操作员将处于基数模式。主程序如下所示:
;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.