裂变,1328 989 887 797字节
这个答案有点长(我希望我们有可折叠的区域)...请不要忘记滚动过去,并向其他答案展示一些爱!
编写此代码是激发这一挑战的原因。我想在Fission中为EOEIS添加一个答案,这使我进入了这个序列。但是,实际上学习裂变并实施此过程需要花费几周的时间来完成。在此期间,这个序列确实对我产生了影响,所以我决定为此提出一个单独的挑战(再者,无论如何,在EOEIS上都不会特别困难)。
因此,我向您介绍Monstrosity:
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
9\ ; 7A9
SQS {+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \ D /8/
~4X /A@[ %5 /; & K } [S//~KSA /
3 \ A$@S S\/ \/\/\/ \/>\ /S]@A / \ { +X
W7 X X /> \ +\ A\ / \ /6~@/ \/
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
; \@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
W /MJ $$\\ /\7\A /;7/\/ /
4}K~@\ &] @\ 3/\
/ \{ }$A/1 2 }Y~K <\
[{/\ ;@\@ / \@<+@^ 1;}++@S68
@\ <\ 2 ; \ /
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
它期望输入中没有结尾的换行符,因此您可能希望将其命名为echo -n 120 | ./Fission oeis256504.fis
。
布局很可能仍然要高效得多,所以我觉得还是有很多可以改进的空间在这里(例如,这包含911 581 461 374位)。
在进行解释之前,请先进行测试说明:官方口译员不能完全按原样工作。a)Mirror.cpp
不能在许多系统上编译。如果遇到此问题,只需注释掉有问题的行-此代码中未使用受影响的组件(随机镜像)。b)有一些错误可能导致不确定的行为(这种复杂的程序很可能会导致错误)。您可以应用此修补程序来修复它们。完成此操作后,您应该可以使用以下命令编译解释器
g++ -g --std=c++11 *.cpp -o Fission
有趣的事实:该程序几乎使用了Fission提供的所有组件,除了#
(随机镜),:
(半镜)-
或|
(普通镜)和"
(打印模式)。
到底怎么回事?
警告:这将是很长的时间...我假设您对Fission的工作方式以及如何编程的想法非常感兴趣。因为如果您不这样做,我不确定该如何总结。(尽管下一段给出了对该语言的一般描述。)
裂变是一种二维编程语言,其中数据和控制流都由在网格中移动的原子表示。如果您以前曾经看过或使用过Marbelous,则这个概念应该很熟悉。每个原子都有两个整数性质:非负质量和任意能量。如果质量变为负数,原子将从网格中移出。在大多数情况下,您可以将质量视为原子的“值”,而将能量视为某种元属性,该元属性被多个组件用来确定原子的流动(即,大多数开关取决于原子的符号)。能量)。(m,E)
如有必要,我将用表示原子。在程序开始时,网格以一堆(1,0)
原子从四个位置上的任意位置放置UDLR
(字母表示原子最初移动的方向)。然后,板上装有一堆组件,这些组件会改变原子的质量和能量,改变其方向或执行其他更复杂的操作。有关完整列表,请参见esolangs页面,但在本说明中将介绍其中的大多数内容。另一个重要点(程序会多次使用该点)是网格是环形的:撞到任一侧的原子重新出现在相反的一侧,朝相同的方向移动。
我用几个较小的部分编写了程序,并在最后将它们组装起来,所以这就是我的解释方法。
atoi
这个组件看似没有意思,但是它很好而且很简单,可以让我介绍Fission的算术和控制流程的许多重要概念。因此,我将详细介绍这一部分,以便减少其他部分,以介绍新的Fission机制,并指出更高级的组件,您应该可以对其进行详细的控制。
裂变只能读取单个字符的字节值,而不能读取整数。虽然这是可以接受的做法,但我想着可以做到这一点,并且可以正确地解析STDIN上的实际整数。这是atoi
代码:
;
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
O
裂变反应堆是聚变反应堆中最重要的两个组成部分。裂变反应堆是任何一种V^<>
(上述代码使用<
和>
)。裂变反应堆可以存储原子(通过将其发送到角色的楔子中),默认值为(2,0)
。如果一个原子碰到角色的顶点,则将两个新原子发送到侧面。通过将入射质量除以存储质量(即默认减半)来确定其质量-左原子获得该值,右原子获得质量的其余部分(即,裂变中的质量守恒) 。两个出射原子的入射能量都为负储存的能量。这意味着我们可以使用裂变反应堆进行算术运算-进行减法和除法运算。如果从该地点撞上裂变反应堆,则原子将简单地沿对角线反射,然后沿角色顶点的方向移动。
聚变反应堆是任何一种YA{}
(以上代码使用Y
和{
)。它们的功能相似:它们可以存储一个原子(默认值(1,0)
),当从顶点撞击时,两个新原子将被发送到侧面。但是,在这种情况下,两个原子将是相同的,始终保留输入能量,并将输入质量乘以存储的质量。也就是说,默认情况下,聚变反应堆只是复制任何撞击其顶点的原子。当从侧面撞击时,聚变反应堆会稍微复杂一些:原子也(独立于其他内存)存储,直到原子撞到另一侧。当发生这种情况时,一个新原子向顶点方向释放,其质量和能量是两个旧原子的总和。如果一个新原子在匹配原子到达另一侧之前撞到同一侧,则旧原子将被简单地覆盖。聚变反应堆可用于实现加法和乘法。
我想忽略的另一个简单组件是[
,]
它只需将原子的方向分别设置为右和左(无论传入方向如何)。垂直等效项是M
(向下)和W
(向上),但是它们未用于atoi
代码。释放初始原子后UDLR
也充当WM][
。
无论如何,让我们看看那里的代码。该程序以5个原子开始:
- 的
R
和L
在底部简单地得到它们的质量增量(与+
)成为(10,0)
然后分别存储在裂变和聚变反应堆,。我们将使用这些反应堆来解析以10为基的输入。
- 的
L
在右上角被它的质量递减(用_
),以成为(0,0)
与被存储在聚变反应堆的侧Y
。这是为了跟踪正在读取的数字-在读取数字时,我们将逐渐增加并乘以该数字。
- 用
R
设置左上角的质量为0
(48)的字符代码'0
,然后与质量和能量交换,@
最后质量增加一次,+
得到(1,48)
。然后用对角镜将其重定向\
并/
存储在裂变反应堆中。我们将使用48
for减法将ASCII输入转换为数字的实际值。我们还必须增加质量1
以避免被除以0
。
- 最后,
U
左下角的实际使一切运动,最初仅用于控制流。
重定向到右侧后,控制原子命中?
。这是输入组件。它读取一个字符,并将原子的质量设置为读取的ASCII值,将能量设置为0
。如果我们改为EOF,则能量将设置为1
。
原子继续,然后撞击%
。这是一个镜像开关。对于非正能量,这就像/
一面镜子。但是对于正能量,它的作用类似于\
(并且也将能量减1)。因此,当我们读取字符时,原子将向上反射,我们可以处理字符。但是,当我们完成输入后,原子将向下反射,我们可以应用不同的逻辑来检索结果。仅供参考,相反的成分是&
。
因此,我们现在有了一个前进的原子。我们要为每个字符执行的操作是读取其数字值,将其添加到我们的运行总计中,然后将该运行总计乘以10以准备下一个数字。
角色原子首先击中(默认)聚变反应堆Y
。这会拆分原子,我们使用左手的副本作为控制原子,以循环回到输入组件并读取下一个字符。正确的副本将被处理。考虑一下我们已经读过字符的情况3
。我们的原子将会(51,0)
。我们与交换质量和能量@
,以便可以利用下一个裂变反应堆的减法。反应器减去48
了能量(不改变质量),因此它发出了两个副本(0,3)
-能量现在对应于我们已读取的数字。即将复制的副本;
仅用丢弃(一个会破坏所有传入原子的组件)。我们将继续处理后续副本。您需要遵循其路径/
并\
镜像一点。
在@
之前的核聚变反应堆再次交换物质和能量,这样我们将添加(3,0)
到我们的运行总和的Y
。请注意,运行总计本身将始终具有0
能量。
现在J
是一个跳跃。它的作用是通过其能量使任何传入的原子向前跳。如果是0
,原子将一直继续前进。如果是1
,它将跳过一个单元格,如果它将2
跳过两个单元格,依此类推。能量是花在跳跃中的,所以原子总是以能量结束0
。由于运行中的总能量确实为零,因此暂时忽略该跳跃,并将原子重定向到聚变反应堆{
,该反应堆的质量乘以10
。下降的副本将被丢弃,;
而上升的副本将Y
作为新的运行总量反馈到反应堆中。
上面的代码一直重复(以一种有趣的流水线方式,在处理完新数字之前先处理新数字),直到我们达到EOF为止。现在,%
将向下发射原子。这个想法是(0,1)
在击中正在运行的总反应堆之前将这个原子变成现在,这样a)总原子不会受到影响(零质量),并且b)我们获得了1
跳过原子核的能量[
。我们可以轻松地利用来照顾能量$
,这会增加能量。
问题是,?
当您按下EOF时,不会重设质量,因此质量仍将是读取的最后一个字符的质量,并且能量将保持不变0
(因为%
减小1
到0
)。因此,我们想摆脱那种麻烦。为此,我们@
再次交换了质量和能量。
在完成本节之前,我需要再介绍一个组件:Z
。这基本上与%
或相同&
。不同之处在于,它使正能量原子直接通过(同时减少能量)并使非正能量原子向左偏转90度。我们可以使用它通过Z
反复遍历一个原子来消除它的能量-一旦能量消失,原子就会发生偏转并离开回路。这就是这种模式:
/ \
[Z/
一旦能量为零,原子将向上移动。在程序的其他部分,我将以一种形式或另一种形式多次使用此模式。
所以,当原子离开这个小圈,这将是(1,0)
与交换到(0,1)
被@
击打的核聚变反应堆释放输入的最终结果之前。但是,运行总计将减少10倍,因为我们已经尝试将其乘以另一个数字。
所以现在有了能量1
,这个原子将跳过[
并跳入原子/
。这会将其偏转到裂变反应堆中,我们已经准备好将其除以10并修复无关的乘法。再次,我们丢弃带有的一半,;
而保留另一半作为输出(在此表示O
,它将简单地打印相应的字符并破坏原子-在完整程序中,我们将继续使用原子)。
itoa
/ \
input -> [{/\ ;@
@\ <\
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
当然,我们还需要将结果转换回字符串并打印出来。这就是本部分的目的。假设输入没有在第10个滴答声之前到达,但是在完整的程序中很容易给出。该位可以在完整程序的底部找到。
该代码引入了一个非常强大的Fission新组件:stack K
。堆栈最初是空的。当具有非负能量的原子撞击电池堆时,该原子被简单地推到电池堆上。当具有负能量的原子撞击堆栈时,其质量和能量将被堆栈顶部的原子替换(从而被弹出)。如果堆栈为空,则原子的方向相反,其能量变为正(即乘以-1
)。
好了,回到实际的代码。该itoa
代码段的想法是反复对输入取10取模以找到下一个数字,同时将输入除以10进行下一次迭代。这将产生相反顺序的所有数字(从最低有效到最高有效)。为了固定顺序,我们将所有数字推入堆栈,最后将它们逐一弹出以进行打印。
该代码的上半部分执行数字计算:L
加号的加号为10,我们将其克隆并输入到裂变和聚变反应堆中,因此我们可以除以10,然后乘以10。循环基本上从[
左上角的。当前值被分割:一个副本除以10,然后乘以10并存储在裂变反应堆中,然后在顶点处被另一个副本击中。计算结果i % 10
为i - ((i/10) * 10)
。还要注意,在A
除法之后和乘法之前拆分中间结果,这样我们就可以i / 10
进入下一个迭代。
%
一旦迭代变量达到0 ,中止循环。由于这或多或少是一个do-while循环,因此该代码甚至可以用于打印0
(否则将不创建前导零)。离开循环后,我们想清空堆栈并打印数字。S
与的相反Z
,因此它是一个开关,它将使非正能量的入射原子向右偏转90度。因此,原子实际上在S
直线上从边缘移到,K
从而弹出一个数字(请注意,~
这确保了传入的原子具有能量-1
)。该数字将递增48
以获取相应数字字符的ASCII码。该A
分裂数字打印一个副本!
并将另一份副本反馈到Y
反应堆中以获取下一位数字。所打印的副本将用作堆栈的下一个触发器(请注意,镜像还会将其发送到边缘周围,以M
从左侧命中)。
当堆栈为空时,K
会反射原子并将其能量转化为+1
,使其直接穿过S
。N
打印换行符(只是因为它很整洁:))。然后原子R'0
再次通过原子到达原子的侧面Y
。由于周围没有其他原子,因此它将永远不会释放,并且程序将终止。
计算裂变数:框架
让我们看一下程序的实际内容。该代码基本上是我的Mathematica参考实现的一部分:
fission[n_] := If[
(div =
SelectFirst[
Reverse@Divisors[2 n],
(OddQ@# == IntegerQ[n/#]
&& n/# > (# - 1)/2) &
]
) == 1,
1,
1 + Total[fission /@ (Range@div + n/div - (div + 1)/2)]
]
其中div
是最大分区中的整数数。
主要区别在于我们无法处理Fission中的半整数值,因此我正在做很多事情乘以2的事情,并且Fission中没有递归。要解决此问题,我将所有整数推送到队列中的分区中,以便稍后处理。对于我们处理的每个数字,我们将计数器增加1,一旦队列为空,我们将释放该计数器并将其发送出去进行打印。(队列的Q
工作方式与完全相同K
,只是按FIFO顺序。)
这是此概念的框架:
+--- input goes in here
v
SQS ---> compute div from n D /8/
~4X | /~KSA /
3 +-----------> { +X
initial trigger ---> W 6~@/ \/
4
W ^ /
| 3
^ generate range |
| from n and div <-+----- S6
| -then-
+---- release new trigger
最重要的新组件是数字。这些是传送器。具有相同数字的所有传送器属于同一个人。当原子击中任何传送器时,它将立即移动同一组中的下一个传送器,其中下一个按通常的从左到右,从上到下的顺序确定。这些不是必需的,但有助于布局(因此可以打一点高尔夫球)。还有X
一个简单地复制一个原子,直接向前发送一个副本,向后发送另一个副本。
到目前为止,您也许可以自己整理出大部分框架。左上角的值队列仍待处理,并一次释放一个n
。一个副本n
被传送到底部,因为在计算范围时我们需要它,另一个副本进入顶部的块中进行计算div
(这是代码中最大的单个部分)。一旦div
已经计算出来,它被复制-一份递增右上角,这是存储在一个柜台K
。另一份副本被传送到底部。如果div
为was 1
,我们将立即向上偏转它,并将其用作下一次迭代的触发器,而无需排队任何新值。否则,我们使用div
和n
在底部的部分中生成新的范围(即具有相应质量的原子流,随后将其放入队列中),然后在范围完成后释放新的触发器。
队列为空后,触发器将被反射,直接穿过S
并再次出现在右上角,从那里释放计数器(最终结果)A
,然后将其传送到itoa
via 8
。
计算裂变数:环体
因此,剩下的就是计算div
和生成范围的两个部分。计算div
是这一部分:
;
{+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \
/A@[ %5 /; & K } [S/
\ A$@S S\/ \/\/\/ \/>\ /S]@A / \
X X /> \ +\ A\ / \ /
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
\@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
\ /\7\A /;7/\/
您现在可能已经看够了,可以耐心为自己解决这个问题。高层细分是这样的:前12列左右产生的除数流2n
。接下来的10列会过滤掉那些不满足的列OddQ@# == IntegerQ[n/#]
。接下来的8列过滤掉那些不满足的列n/# > (# - 1)/2)
。最后,我们将所有有效除数推入堆栈,完成后,将整个堆栈清空到聚变反应堆中(覆盖除最后一个/最大除数之外的所有除数),然后释放结果,然后消除其能量(不是-从检查不等式为零)。
那里有很多疯狂的路径实际上并没有做任何事情。主要\/\/\/\/
是顶部的疯狂(5
s也是它的一部分)和围绕底部的一条路径(穿过7
s)。我不得不添加这些以应对某些恶劣的比赛条件。裂变可能会使用延迟组件...
从生成新的范围的所述代码n
和div
是这样的:
/MJ $$\
4}K~@\ &] @\ 3/\
\{ }$A/1 2 }Y~K <\
\@ / \@<+@^ 1;}++@
2 ; \ /
我们首先进行计算n/div - (div + 1)/2
(两个条件下限,得出相同的结果)并存储以备后用。然后,我们生成一个从div
下到下的范围1
,并将存储的值添加到每个范围。
在这两种方法中,我应该提到两种新的常见模式:一种是“ 从下方” SX
或“ ZX
点击”(或旋转版本)。如果您想直接复制一个原子,这是复制原子的一种好方法(因为重定向聚变反应堆的输出有时很麻烦)。的S
或Z
旋转原子入X
,然后旋转镜像副本回传播的初始方向。
另一种模式是
[K
\A --> output
如果我们在其中存储任何值,K
则可以通过K
从顶部击中负能量来反复检索它。A
重复项将复制我们感兴趣的值,然后将下次复制的内容立即发送回堆栈。
好吧,那真是个书呆子……但是,如果您真的了解了这一点,我希望您能想到Fissioni͝s̢̘̗̗͢i̟nç̮̩r̸̭̬̱͔e̟̹̟̜͟d̟̹̟̜͟i̠͙͎̖͓̯b̘̠͎̭̰̼l̶̪̙̮̥̮y̠̠͎̺͜ ͚̬̮f̟͞u̱̦̰͍n͍̜̠̙t̸̳̩̝o̫͉̙͠p̯̱̭͙̜͙͞ŕ̮͓̜o̢̙̣̭g̩̼̣̝r̤͍͔̘̟ͅa̪̜͇m̳̭͔̤̞ͅ ͕̺͉̫̀ͅi͜n̳̯̗̳͇̹。