Brain-Flak打高尔夫球的技巧


24

Brain-flak是一种基于堆栈的图灵语言,由我,DJMcMayhem1000000000合作编写。

一些用户对Brain-Flak的神秘方式非常有经验。因此,我认为最好将这个问题设置为一种方法,以便我们以及希望其他人也可以与社区分享我们的知识,并降低使用这种“被设计成难以使用”的语言的准入门槛。甚至甚至可以教会我们更多的经验的人一些新的技巧。

那么,您有什么技巧可以使人脑部打高尔夫球?我正在寻找可用于一般性地解决高尔夫问题的想法,这些想法至少在某些方面是针对大脑问题的(例如,“删除评论”不是答案)。

请为每个答案发布一个提示。

Answers:


22

使用第三叠

如果您阅读过标题,可能会有些困惑。当然,Brain-Flak中只有两叠吗?但是,我向您保证,它的存在并且是Brain-Flak写作和打高尔夫球中最强大的工具之一,即使不是最强大的工具。


什么是“第三叠”?

每个Brain-Flak程序都以一种或另一种方式使用第三个堆栈,但是大多数使用都是在后台进行的,通常只需忽略它存在的事实,通常会很有用。程序中的每个括号都可以从堆栈中添加或删除一项。三个开括号([<都将一个项目添加到堆栈中,而三个共轭词)]>都将一个项目从堆栈中删除。堆栈中项目的值是程序当前作用域的值,使用nilads将以某些方式修改此值。紧密括号)具有将元素从“第三堆栈”移动到当前堆栈的独特功能。一推。

希望对您来说这已经很清楚了。第三堆栈是某种类型的堆栈,可以记住已经执行的代码的返回值。让我们来看一个简单的程序示例,该程序跟踪两个普通堆栈和第三个堆栈。

我们将逐步完成以下程序。该程序压-3, 1, -2入堆栈。

(([()()()])(()))

我们从三个开括号开始,它们都将零推到第三堆栈。

现在我们的堆栈看起来像这样,第三个堆栈在右侧,活动堆栈下面有一个^

        0
        0
  0  0  0
  ^

(([()()()])(()))
   ^

现在我们有三个()尼拉德。它们对正常的两个堆栈没有任何作用,但是它们每个都会在“第三堆栈”的顶部添加一个,从而使我们的堆栈看起来像:

        3
        0
  0  0  0
  ^

(([()()()])(()))
         ^

现在,我们遇到了一个]如前所述的大括号,它从第三个堆栈中删除了一个项目,但]具有从堆栈顶部减去它删除的元素的功能。因此,我们的新堆栈将如下所示:

       -3
  0  0  0
  ^

(([()()()])(()))
          ^

这很有道理;[...]取反,因此]应向下减去。

现在我们必须执行一个)。您可能还记得)程序中将内容压入堆栈的位置,因此我们会将“第三堆栈”的顶部移至当前堆栈,此外,还将“添加” -3到第三堆栈中的下一个元素。

 -3  0 -3
  ^

(([()()()])(()))
           ^

再次遇到三个开放括号之一,因此我们将另一个元素添加到“第三堆栈”中。

        0
 -3  0 -3
  ^

(([()()()])(()))
            ^

就像我们之前说的那样(),第三栈的顶部将增加一。

        1
 -3  0 -3
  ^

(([()()()])(()))
              ^

并将)第三堆栈的顶部移至活动堆栈并向下添加

  1
 -3  0 -2
  ^

(([()()()])(()))
               ^

最后一个)将第三堆栈移动到活动堆栈上,并且由于在第三堆栈上没有剩余要添加的元素,因此不执行其他任何操作。

 -2
  1
 -3  0
  ^

(([()()()])(()))
                ^

程序结束了,所以我们终止并输出。


该示例旨在使您了解“第三堆栈”的作用和作用。它不包括所有操作,但希望您能弄清楚每个操作各自的作用。如果您仍在挣扎中,我会在此答案的底部添加一个“备忘单”以帮助您。

好吧那又怎样

好的,现在您了解了第三堆栈,但是“那又如何”?即使您没有将其称为“第三叠”,您也已经在使用它,关于第三叠的思考如何帮助您打高尔夫?

让我们看一个问题。您想取一个数字三角形。这是小于n的所有数字的总和。

一种方法可能是在堆外创建一个累加器,并在递减计数时将其添加到累加器中。这将创建如下代码:

(<>)<>{(({}[()])()<>{})<>}{}<>({}<>)

在线尝试!

这段代码非常紧凑,可能有人认为它不可能变得更小。但是,如果我们从第三个堆栈的角度进行处理,则很明显这是非常低效的。不用将累加器放在堆外,我们可以使用a将其放在第三个堆栈上,(并在我们使用的末尾检索它)。我们将再次遍历所有数字,但是这一次我们不需要做任何事情来增加我们的Third Stack,程序会为我们完成。看起来像:

({()({}[()])}{})

在线尝试

此代码小于我们之前制作的高尔夫球版本的一半。实际上,计算机搜索已证明该程序是可以执行此任务的最短程序。可以使用“所有运行的总和”方法来解释该程序,但是当使用“第三堆栈”方法进行解释时,我认为它会更加直观和清晰。

什么时候使用第三叠?

理想情况下,每当您开始研究Brain-Flak的新问题时,您都应该考虑一下我该如何考虑“第三堆”。但是,作为一般经验法则,每当您必须跟踪某种类型的累加器或拥有总计运行时,最好将其放在第三个堆栈上而不是两个实际堆栈上。

另外一次考虑使用第三个堆栈可能是一个好主意,那就是您没有足够的空间在其他两个堆栈上存储某些值。当您在两个现有堆栈上进行操作并且想要保存一个值以供以后使用而不必跟踪其位置时,这特别有用。

第三叠的局限性

第三堆栈在许多方面都非常强大,但它也有其自身的局限性和缺点。

首先,在编译时确定任何给定点的第三堆栈的最大堆栈高度。这意味着,如果要在堆栈上使用一定数量的空间,则在编写程序时必须分配该空间。

其次,第三个堆栈不是随机访问。这意味着您不能对除最高值之外的任何值执行任何操作。另外,您不能在堆栈上移动值(例如交换前两个元素)。

结论

第三堆栈是一个功能强大的工具,我认为它对于每个Brain-Flak用户都是必不可少的。这需要一些时间来适应,并且需要改变您对Brain-Flak编程的思考方式,但是如果使用得当,则在打高尔夫球时,一切都将变得体面而出众。

备忘单

以下是操作及其对第三堆栈的影响的列表

 Operation  |                 Action
====================================================
   (,[,<    | Put a zero on top of the Third Stack
----------------------------------------------------
     )      | Add the top of the Third Stack to the
            | second element and move it to the 
            | active stack
----------------------------------------------------
     ]      | Subtract the top of the Third Stack
            | from the second element and pop it
----------------------------------------------------
     >      | Pop the top of the Third Stack
----------------------------------------------------
     ()     | Add one to the top of the Third Stack
----------------------------------------------------
     {}     | Pop the top of the active stack and
            | add it to the top of the Third Stack
----------------------------------------------------
     []     | Add the stack height to the Third
            | Stack
----------------------------------------------------
   <>,{,}   | Nothing

1
哇,棒极了!当我看到这个时,我实际上只是来这里写一个类似的答案。+1!我更喜欢将其视为The Accumulator,但是Third Stack的隐喻确实很有趣。
DJMcMayhem

我一直称其为“工作区”或“工作区”。
sagiksp

在Brainflak的TIO版本上,{...}返回所有迭代的总和。
CalculatorFeline

@CalculatorFeline是的,除了一些非常早期的版本以外,几乎在所有版本的Brain-Flak上都是如此。但是我不确定您为什么特别在这篇文章上发表评论。
小麦巫师

<>,{,} | Nothing
CalculatorFeline

21

求模数/余数

查找nm是基本的算术运算之一,对许多挑战都很重要。对于m> 0n> = 0的情况,可以使用以下46字节的代码段。假定n在活动堆栈的顶部,而m在活动堆栈的顶部,下一个向下,并用n mod m替换它们。它使其余堆栈完好无损。

({}(<>))<>{(({})){({}[()])<>}{}}{}<>([{}()]{})

此带注释的版本在程序中的某些位置显示了堆栈内容。;分隔两个堆栈并.标记活动堆栈。

. n m;
({}(<>))<>
{   . m; r 0   (r = n - km)
    (({}))
    . m m; r 0
    {({}[()])<>}
    {}
}
m-n%m-1 m; . 0
{}<>([{}()]{})
. n%m;

我花了一段时间才了解未注释部分的功能({({}[()])<>}),但是一旦我弄清楚了……天才:-)
ETHproductions

11

一键通冗余

这是一个很大的。这也是一个细微的差别。

这个想法是,如果您先推入某个东西然后不做任何事情就弹出它,则根本不应该推入它。

例如,如果您想将某些东西移到堆外然后添加一个,您可以编写以下代码:

({}<>)({}())

这样可以更简单:

({}<>())

第一个程序拾取该项目,将其移动,再次拾取它并添加一个,而第二个程序一次完成两个动作。

这个例子很简单,但是会变得更加复杂。例如:

({}<({}<>)><>)(<((()()()){}[((){}{})])>)

此处的减少不太清楚,但是可以通过第二次按下来减少程序中的第四次弹出,如下所示:

(<((()()()){}[((){}<({}<>)><>{})])>)

这是我的高尔夫曲目中最强大的工具,但要熟练掌握它就需要一些练习。完成一段时间后,您几乎可以立即发现它们。


在后一个示例中,第一对括号中的部分不等于({}<{}>)吗?
feersum '16

@feersum不,不是。它将堆栈中第二个项目的副本移到堆栈外,同时({}<{}>)完全破坏该项目。但是,这些程序并不是仅仅为了突出此处的工作原理而并不是最佳选择。
小麦巫师

6

优化整数

整数很难代表Brain-Flak。幸运的是,我们有一个问题可以为您帮助“ Golf Brain-Flak Integer”高尔夫。(请注意,该问题旨在将整数压入堆栈,因此push-pop冗余可能适用于更实际的程序。)


请注意,我们也有brain-flak.github.io/integer/,为了方便起见,它在线运行这些算法之一。
DJMcMayhem

@DrMcMoylex现在,我们需要像在Brain-Flak本身中实现整数metagolfer一样!
尼尔


5

推送额外的循环计数器

通常,您会想要做类似的事情

对堆栈中的每个元素执行X操作

要么

对堆栈中每对相邻元素执行X操作

当输入中可能包含“ 0”,或者操作X的结果可能为0时,这确实很不方便。因为您需要执行以下操作:

([])
{

  {}

  ({}...<>)
  ([])

}

对每个元素做X,然后再做

<>
([])
{
  {}
  ({}<>)
  <>
  ([])
}
<>

再次反转阵列。

更糟糕的是,如果我们要对成对的相邻元素进行操作,则需要([][()])代替([])。这真的很不方便。诀窍是:对每个元素执行X运算时,将1推到上方的备用堆栈上X(element)。然后,当您将其反转时,您只需

<>
{
  {}
  ({}<>)
  <>
}
<>

它短了8个字节,因此当您将额外的代码放入1时,最终将节省4-6个字节。对于更具体的示例,比较获取数组增量的任务。没有这个技巧,您需要:

([][()]){

    {}

    ([{}]({})<>)<>

    ([][()])

}{}{}<>

([])
{{}({}<>)<>([])}<>

62岁。有了这个把戏,您将拥有

([][()]){

    {}

    ([{}]({})<>)(())<>

    ([][()])

}{}{}<>

{{}({}<>)<>}<>

对于58。如果使用正确的方法(例如,先反转,然后再删除两个([][()])),则在特殊情况下可以节省更多。


3

利用“堆栈高度” nilad

特别是在挑战或您始终知道输入大小的挑战中,您可以利用'Stack-Height'nilad []来创建整数。

让我们通过一个假设的挑战来解决这个问题:output CAT。非高尔夫的方法是使用在线整数高尔夫球手将67、65 和84推入。这给出了:

(((((()()()()){}){}){}()){}())

(((((()()()()){}){}){}){}())

((((((()()()){}()){}){})){}{})

(为清晰起见,使用换行符)。这是88个字节,并不是很大。如果我们改用值之间的连续差,则可以节省很多。因此,我们将第一个数字包装在push调用中,然后减去2:

(   (((((()()()()){}){}){}()){}())  [()()] )

然后,我们采用以下代码,将其包装在push调用中,并在末尾添加19:

(  ((((((()()()()){}){}){}()){}())[()()])   ((((()()()){})){}{}()) )

这是62个字节,用于高达26个字节的高尔夫!

现在在这里,我们可以利用堆栈高度nilad的优势。当我们开始推19时,我们知道堆栈中已经有2个项目,因此[]将求和2。我们可以使用它来创建更少字节的19。显而易见的方法是将内部更改()()()()[]。不过,这只会节省两个字节。进行更多修改后,我们可以将

((([][]){})[]{})

这为我们节省了6个字节。现在我们降至56。

您可以看到这些提示已非常有效地用于以下答案:


您的CAT程序实际上推动了TAC:P
Wheat

另外,52个字节
Wheat

2
我知道这是一个技巧,但我不能忍受,50个字节
Wheat

1
另一个有助于解决滥用问题的怪异但有时有效的技巧[]:在代码前0(<>)s。仅当您打算将代码反向推入时,这才真正起作用,但是如果遇到正确的数字,则可以减少很多代码。我添加了6 s的一个极端例子0,这使我可以使用[]与我一样多的s()
Jo King

2

使用维基

我们有一个维基!它有一些缺点,但是它是有用的资源。它包含有用的,打高尔夫球的,堆栈干净的程序列表,您可以将其粘贴到代码中。如果您想做某事,您认为某人可能在Wiki上就很有可能已经做了。


2

更好的循环

这是一个简单的方法:

一个相当常见的构造是:

(({})<{({}[()]<...>)}{}>)

您想循环n次但仍保留n的位置。但是,可以这样写:

({<({}[()]<...>)>()}{})

保存2个字节。

另一个相当常见的范例是

([])({<{}>...<([])>}{})

这将循环并累积整个堆栈。由于一些花哨的数学,这与以下内容相同:

(([]){[{}]...([])}{})

1

检查你的负面

有时,您可以通过策略性地选择对[...]单子取反的方法来打高尔夫球。

一个简单的例子是在[...]s中。例如[()()()[()()]]可能只是[()()()]()()

假设您要检查一个值是否是任何起始括号(<{[。最初的尝试是推动每个字符之间的差异并循环减去它

({}<(((((       #Push the differences between start bracket characters
(((()()()()){}){}){})   #Push 32
[()])                   #Push 31
[((()()())()){}{}])     #Push 20
){})><>)                #Push 40
<>({<(([{}]<>{}))>(){[()](<{}>)}<>})

但是,您可以通过推送差异的负版本来节省4个字节!

({}<(((((       #Push the differences between start bracket characters
((([()()()()]){}){}){}) #Push -32
())                     #Push -31
((()()())()){}{})       #Push -20
){})><>)                #Push -40
<>({<(({}<>{}))>(){[()](<{}>)}<>})

通常,这并不能为您节省很多,但是也可以花费很少的精力来更改周围的[...]环境。请留意可以将计数器的负数而不是正数推入以节省多次递增而不是以后递减的情况。或换出(a[b])([a]b)之间做出两个数字负,而不是积极的差异。


1
类似的事情可以用零<a<b>c>-> <abc><a[b]c>->完成<abc>
小麦巫师
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.