一段时间以来,一直困扰着我,尽管我了解Brainfuck的基本原理,但我无法牢牢掌握该语言的高级功能。因此,我开始重新评估我对语言的了解。

Brainfuck的记忆由一堆细胞组成。可以通过将指针指向每个单元格并递增或递减其值来修改每个单元格。最基本的运算符是+-.。这些运算符将分别增加,减少和打印当前单元格中的数据。打印操作员将获取当前值,并将相应的ASCII字符显示在屏幕上。

要打印基本句子:


您好,世界! >

我写了以下内容:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.  Increment to 'H' and print
+++++++++++++++++++++++++++++.                                             Increment to 'e' and print
+++++++.                                                                   Increment to 'l' and print
.                                                                          Value is already at 'l' so just print
+++.                                                                       Increment to 'o' and print
-------------------------------------------------------------------.       Decrement to comma and print
------------.                                                              Decrement to ' ' and print
+++++++++++++++++++++++++++++++++++++++++++++++++++++++.                   Increment to 'W' and print
++++++++++++++++++++++++.                                                  Increment to 'o' and print
+++.                                                                       Increment to 'r' and print
------.                                                                    Decrement to 'l' and print
--------.                                                                  Decrement to 'd' and print
-------------------------------------------------------------------.       Decrement to '!' and print


用这种风格写Brainfuck来打印句子很简单。遍历句子并针对每个字符执行以下操作:


计算当前字符与前一个字符之间的差异
编写与字符之间的差异相同的+-+如果当前字符高于先前字符,-如果低于先前字符
编写.以打印当前字符

上面看到的结果看起来并不差。因为所有操作都在同一单元上进行,所以它的内存效率很高。但是速度也很重要。不幸的是,我还不知道如何正确安排Brainfuck的执行时间。

我相当确定这不是编写此脚本的“最佳” /“惯用”方式。我知道Brainfuck支持一个while构造,这在这里似乎是个好主意,但我还没有找到最明显的实现。我已经看过Simon的方法,但是我还没有达到这个水平。肯定会让我重复很多。

我目前的实施情况有多糟?欢迎提供有关如何逐步将其改进为Brainfuck的更标准样式(使用循环代替重复)的指针。

#1 楼

Brainfuck乘法

详细介绍200_success的乘法,以及“较短的,可读性较低的版本,使用相似的思想,但具有更多的单元重用”:

ASCII值正如200_success所提到的那样,您想要写的是:

72 101 108 108 111 44 32 87 111 114 108 100 33


这些就是我们要生成的数字。让我们从对它们进行排序开始:

32 33 44 72 87 100 101 108 108 108 111 111 114


这里我们注意到我们可以将数字分为不同的组。如何做到这一点有点基于意见,但我会选择30-50个小组,70-90个小组和100个以上的小组,这使我们留给了以下小组:

32 33 44
72 87
100 101 108 108 108 111 111 114


现在的想法是使用乘法得到三个数字,记住要首先写的每个组中的哪个数字很重要。回头看我们的ASCII值列表(按它们出现的顺序排序),我们看到我们想要72、101和44。使用一些数学,我们知道\ $ 72 = 7 * 10 + 2 \ $,\ $ 44 = 4 * 10 + 4 \ $和\ $ 101 = 10 * 10 +1 \ $。

生成数字

生成这些数字在BF中,我们将执行典型的BF构造:

a++++[-b++++a]


ab表示“转到单元格a / b”。使用的+标志的数量随您要生成的数量而变化。由于我们要生成三个不同的数字,因此可以使用以下代码一次完成所有操作:不是零”,这是BF支持的唯一条件和循环结构,实际上,这是您真正需要的唯一东西。您只需要知道如何使用它。

由于我们已经在单元格[...]上开始,我们可以像这样编写我们的结构:

a++++[-b+c++d+++a]


这将分别生成4 * 1、4 * 2和4 * 3。调整a标志的数量使我们拥有:

++++[->+>++>+++<<<]


即10 * 4、10 * 7和10 *10。

所以现在我们可以使用这些值来编写我们的字符串。我们有40、70和100的值,因此我们将转到不同的单元格并上下稍微更改它们的值,以获取并打印所需的数字。

+++++ +++++ [-> ++++ > +++++ ++ > +++++ +++++ <<<]


评论和格式

现在我们清理格式并添加一些评论:

+++++ +++++ [-> ++++ > +++++ ++ > +++++ +++++ <<<]
>>++.>+.+++++ ++..+++.<<++++.----- ----- --.>+++++ +++++ +++++.>.+++.----- -.----- ---.<<+.


Brainfuck没有任何内容关于缩进,格式和注释的准则(据我所知),因此如何处理这些部分完全取决于您。

就个人而言,在编写Brainfuck时,我发现保持跟踪非常重要。存储带的值,这就是为什么我更喜欢添加有关存储带的值的注释的原因。然后,如果您要进行任何编辑,则可以通过阅读有关存储磁带值的注释来更轻松地更改要更改的内容。因为所有操作都在同一单元上进行,所以它的内存效率很高。但是速度也很重要。不幸的是,我还不知道如何正确安排Brainfuck执行的时间(尚未)。 >
我在我的Brainfuck解释器中添加了一个功能,使我可以轻松地对此进行计数。您的字符串,此工具生成了以下代码:

+++++ +++++ [-          10 times
  >++++                 10 * 4 = 40
  >+++++ ++             10 * 7 = 70
  >+++++ +++++          10 * 10 = 100
  <<<] 40 70 100
>>++.                     40 72 100 print 'H'
>+.+++++ ++..+++.         40 72 111 print 'ello'
<<++++.----- ----- --.    32 72 111 print comma and space
>+++++ +++++ +++++.       32 87 111 print 'W'
>.+++.----- -.----- ---.  32 87 100 print 'orld'
<<+.                      33 87 100 print '!'


以下是在不同版本的运行时正在执行的指令数量的概述:

$$
\ newcommand {smallm} [0] {\ overset {n \ \ gg \ m} {\ longrightarrow}}
\ begin {array} {| l | c | c |}
\ hline \\&\ textrm {您的代码}&\ textrm {200_success A}&\ textrm {200_success B}&\ textrm {我的代码}&\ textrm {bfdev工具} \\
\ hline \\ \ textrm {下一个}&0&99&69&35&39 \\
\ hline \\ \ textrm {上一个}&0&90&64&34&39 \\
\ hline \\ \ textrm {打印}&13&13&13&13&13 \\
\ hline \\ \ textrm {添加}&193&709&466&256&226 \\
\ hline \\ \ textrm {减}&160&21&22&36&193 \\
\ hline \\ \ textrm {开始时间}&0&1&1&1&6 \\
\ hline \\ \ textrm {End While}&0&10&10&10&33 \\
\ hline \\ \ textrm {Total}&366&943&645&385&549 \\
\ hline
\ end {array} $$

以下是不同版本的源代码长度的概述:

+ 100次更快的方法。但是,使用循环确实会减少代码长度。

我建议您尝试在运行时将更少的代码和更少的指令组合在一起。如果您关心性能,则不应该首先使用Brainfuck;)

另一项小改进

磁带值的顺序确实有一点关系。我意识到您要转到单元格的顺序是70 100 40 70 100 40,但是我的BF程序将单元格保持在40 70 100的顺序。因此,通过对单元格进行稍微重新排序,我保存了2条运行时指令并减少源代码长度减2! (是的!)

我更新的代码:(383条运行时指令,源代码长度为122)

#2 楼

不,那是不可读的代码,因此不是习惯用法。产生大数的典型方法是使用乘法。

"Hello, World!"转换为ASCII值br />
< 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33 >


因此,应该以这种方式编写代码。连续的11可以导致压缩优化。我也发现您的注释不必要地冗长。

此解决方案比原始代码使用更多的存储单元。在我看来,这对于可读性来说是非常值得的。
10 * < 7, 10, 11, 11, 11,  4,  3,  9, 11, 11, 11, 10, 3 >
   + < 2,  1, -2, -2,  1,  4,  2, -3,  1,  4, -2,  0, 3 >


评论


\ $ \ begingroup \ $
“那是不可读的代码,因此很简单”-恩,您知道这是哪种语言吗?
\ $ \ endgroup \ $
–user253751
2015年9月6日于10:22

\ $ \ begingroup \ $
什么?如果可以的话,我可以告诉您(72个加号)。等一目了然。循环使事情变得复杂得多,除了“编码高尔夫”之外,没有任何其他理由。您不会在C中用72表示10 * 7 + 2,对吧?为什么要这样写你的Brainfuck? OP的代码保持简单且速度更快。
\ $ \ endgroup \ $
–林恩
16年8月24日在17:57

\ $ \ begingroup \ $
@Lynn在这里,快速代码和短代码之间需要权衡。我们不使用10 * 7 + 2来编写代码,而是避免编写不必要的代码。我个人的偏爱是在某种程度上最小化代码长度+运行时间指令的值。通过仅使用一个单元格,您必须执行更多的加法和减法,这意味着您必须大量计算每次需要的+/-。通过散布它,您需要更改更多的像元,但每次更改的次数都更少,因此计算111 + 3 = 114比计算111-67 = 44更容易。
\ $ \ endgroup \ $
–西蒙·福斯伯格
16年8月30日在2:44



\ $ \ begingroup \ $
@immibis F ***!像您这样的人鼓励所有那些我写后必须维护的,写得不好的头脑计划。您是否曾经经历过2亿行脑残***调查?我有,要弄清最后一名实习生的工作是不可能的! F***!
\ $ \ endgroup \ $
–玛蒂·乌尔哈克(Mateen Ulhaq)
16 Sep 2 '12:00



\ $ \ begingroup \ $
他们...呃...专有
\ $ \ endgroup \ $
–玛蒂·乌尔哈克(Mateen Ulhaq)
16年11月14日在19:09