Советы по игре в гольф в Brain-Flak

24

Brain-flak - это основанный на стеке язык тьюринга и тарпита, написанный совместно мной, DJMcMayhem и 1000000000 .

Некоторые пользователи очень опытны в таинственных способах Brain-Flak. Поэтому я подумал, что было бы неплохо установить этот вопрос как способ для нас, и, надеюсь, для других, поделиться своими знаниями с сообществом и снизить планку вхождения в этот язык, «созданный, чтобы быть болезненным в использовании». И, возможно, даже научить тех из нас, у кого больше опыта, новым трюкам.

Итак, какие у вас есть советы по игре в гольф? Я ищу идеи, которые могут быть применены к задачам по коду для гольфа в целом, которые, по крайней мере, несколько специфичны для мозгового штурма (например, «удалить комментарии» - это не ответ).

Пожалуйста, оставьте один совет за ответ.

Мастер пшеницы
источник

Ответы:

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 (и получить его в конце, который мы используем ). Мы еще раз пройдемся по всем числам, но на этот раз нам не нужно ничего делать, чтобы увеличить наш третий стек, программа делает это для нас. Это выглядит так:

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

Попробуйте онлайн

Этот код меньше половины размера довольно хорошей версии для гольфа, которую мы сделали ранее. Фактически компьютерный поиск доказал, что эта программа является самой короткой из возможных программ, способных выполнить эту задачу. Эта программа может быть объяснена с использованием подхода «сумма всех прогонов», но я думаю, что она намного более интуитивна и понятна, когда объясняется с использованием подхода третьего стека.

Когда я использую третий стек?

В идеале, всякий раз, когда вы начинаете работу над новой проблемой в Brain-Flak, вы должны подумать про себя, как бы я сделал это с учетом Третьего стека. Однако, как общее практическое правило, когда вам нужно отслеживать какой-то тип аккумулятора или иметь промежуточную сумму, будет хорошей идеей попытаться поместить это в свой третий стек вместо двух реальных стеков.

В другой раз, возможно, стоит подумать об использовании вашего третьего стека, когда у вас нет места для хранения какого-либо значения в двух других стеках. Это может быть особенно полезно, когда вы выполняете манипуляции с двумя существующими стеками и хотите сохранить значение для последующего использования без необходимости отслеживать, где оно находится.

Ограничения третьего стека

Третий стек очень силен во многих отношениях, но у него есть свои ограничения и недостатки.

Во-первых, максимальная высота стека для третьего стека в любой заданной точке определяется во время компиляции. Это означает, что если вы хотите использовать объем пространства в стеке, вы должны выделить это пространство при написании программы.

Во-вторых, третий стек - это не произвольный доступ. Это означает, что вы не можете выполнять какие-либо операции с любым значением, кроме самого верхнего значения. Кроме того, вы не можете перемещать значения в стеке (скажем, поменять местами первые два элемента).

Вывод

Третий стек - это мощный инструмент, и я считаю его необходимым для каждого пользователя Brain-Flak. Это требует некоторого привыкания и изменения вашего мышления о программировании в Brain-Flak, но при правильном использовании это делает разницу между приличным и удивительным, когда дело доходит до игры в гольф.

Cheatsheet

Вот список операций и как они влияют на третий стек

 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! Я предпочитаю думать об этом как о Аккумуляторе , но метафора Третьего стека действительно интересна.
DJMcMayhem
Я всегда называл это «рабочим пространством» или «рабочим стеком».
sagiksp
В версии Brainflak для TIO {...}возвращает сумму всех итераций.
CalculatorFeline
@CalculatorFeline Да, это верно почти для всех версий Brain-Flak, за исключением некоторых очень ранних. Однако я не уверен, почему вы прокомментировали этот пост в частности.
Пшеничный волшебник
<>,{,} | Nothing
CalculatorFeline
21

Нахождение модуля / остатка

Поиск n по модулю m является одной из основных арифметических операций, важных для многих задач. Для случаев m> 0 и n> = 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;
feersum
источник
Мне потребовалось некоторое время, чтобы понять, что сделала аннотированная часть ( {({}[()])<>}), но однажды я понял это ... Genius :-)
ETHproductions
11

Push-Pop Redundancy

Это большой. Это также немного нюанс.

Идея состоит в том, что если вы что-то толкаете, а затем выталкиваете, ничего не делая, вам вообще не следовало толкать это.

Например, если вы хотите переместить что-то в offstack, а затем добавить к нему, вы можете написать следующий код:

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

Это может быть проще, как это:

({}<>())

Первая программа поднимает предмет, перемещает его, снова поднимает и добавляет один, а вторая делает оба за один раз.

Этот пример был прост, однако он может стать намного сложнее. Взять, к примеру:

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

Уменьшение здесь менее очевидно, но 4-е всплывающее окно в программе можно уменьшить с помощью 2-го нажатия, например, так:

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

Это самый мощный инструмент в моем репертуаре для игры в гольф, но для того, чтобы преуспеть в этом, требуется некоторая практика. Как только вы сделаете это некоторое время, вы сможете обнаружить их почти мгновенно.

Мастер пшеницы
источник
В последнем примере, не является ли часть в первой паре скобок эквивалентной ({}<{}>)?
Feersum
@feersum Нет, это не так. Он перемещает копию второго элемента в стеке в выключенный стек, при ({}<{}>)этом полностью уничтожает элемент. Однако эти программы не должны были быть оптимальными только для того, чтобы подчеркнуть принцип работы здесь.
Пшеничный Волшебник
6

Оптимизируйте свои целые числа

Целые числа утомительно представлять в Brain-Flak. К счастью, у нас есть вопрос, который поможет в игре Golf a Brain-Flak Integer . (Обратите внимание, что вопрос предназначен для помещения целого числа в стек, поэтому избыточность push-pop, вероятно, применима к более реалистичным программам.)

Нил
источник
Обратите внимание, что у нас также есть brain-flak.github.io/integer/ , который для удобства запускает один из этих алгоритмов онлайн.
DJMcMayhem
@DrMcMoylex Все, что нам нужно сейчас, как в реализации целочисленного метагольфера в самом Brain-Flak!
Нил
1
У нас это есть. codegolf.stackexchange.com/a/98054/31716 : D
DJMcMayhem
5

Нажмите дополнительные счетчики петли

Часто вы хотите сделать что-то вроде

Выполните операцию X для каждого элемента в стеке

или

Выполните операцию X на каждой паре смежных элементов в стеке

Когда ввод может содержать 0 или результат операции X может дать 0, это действительно неудобно. Потому что вам нужно сделать:

([])
{

  {}

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

}

Делать X каждому элементу, а потом

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

Чтобы снова перевернуть массив.

Еще хуже, если мы оперируем парами смежных элементов, нам нужно сделать ([][()])вместо них ([]). Это действительно неудобно. Вот хитрость: когда вы делаете X для каждого элемента, нажмите 1 на альтернативный стек прямо над X(element). Затем, пока вы меняете его, вы можете просто сделать

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

Это на 8 байт короче, поэтому, когда вы добавите дополнительный код для добавления 1, вы сэкономите 4-6 байт. Для более конкретного примера сравните задачу получения дельт массива. Без этого трюка вам понадобится:

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Для 62. С этим трюком у вас будет

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Для 58. Если использовать правильный путь (например, сначала ([][()])поменять местами , а потом удалить два ), это может сэкономить еще больше в особых сценариях.

DJMcMayhem
источник
3

Воспользуйтесь ниладой высоты стека

Особенно в или задачах, где вы всегда знаете размер входных данных, вы можете использовать преимущество «Stack-Height» []для создания целых чисел.

Давайте разберемся с этим с гипотетической задачей: вывод CAT. Способ не для игры в гольф состоит в том, чтобы использовать онлайн-целочисленного игрока в гольф 67, 65 и 84. Это дает:

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

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

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

(Новые строки для ясности). Это 88 байтов, и не так уж и здорово. Если вместо этого мы выдвигаем последовательные различия между значениями, мы можем сэкономить много. Итак, мы заключаем первый номер в push- вызов и вычитаем 2:

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

Затем мы берем этот код, заключаем его в push- вызов и добавляем 19 в конец:

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

Это 62 байта для колоссального 26 байтового гольфа!

Теперь здесь , где мы получаем , чтобы воспользоваться стек высотой nilad. К тому времени, когда мы начнем нажимать 19, мы знаем, что в стеке уже есть 2 элемента, поэтому []оценим их 2. Мы можем использовать это для создания 19 байтов меньше. Очевидным способом является изменение внутреннего ()()()на ()[]. Это только экономит два байта. Оказывается, с еще большим количеством поворотов мы можем нажать 19 с

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

Это экономит нам 6 байтов. Теперь мы до 56.

Вы можете видеть, что этот совет очень эффективно используется в следующих ответах:

DJMcMayhem
источник
Ваша CATпрограмма на самом деле толкает TAC: P
Wheat Wizard
Также 52 байта
Wheat Wizard
2
Я знаю, что это совет, но я не могу с собой поделать, 50 байтов .
Wheat Wizard
1
Еще один странный, но иногда эффективный совет, чтобы помочь злоупотреблять [] : предваряйте 0s с помощью (<>)s перед вашим кодом. Это действительно работает только в том случае, если вы все равно планируете сдвинуть код в обратном направлении, но если вы найдете правильное число, вы можете немного уменьшить свой код. Довольно экстремальный пример, где я добавляю 6 0с, что позволяет мне использовать столько же, []сколько я использую()
Джо Кинг,
2

Используйте вики

У нас есть вики ! У него есть некоторые недостатки, но это полезный ресурс. В нем есть списки полезных, хорошо отлаженных, чистых программ, которые вы можете вставить в свой код. Если вы хотите сделать что-то, что, по вашему мнению, кто-то мог сделать до того, как есть вероятность, что это в вики.

Мастер пшеницы
источник
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>.
Wheat Wizard