Советы по игре в гольф в Brainfuck

23

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

баран
источник

Ответы:

25

Помещать один совет за ответ было бы слишком много ответов.

  • Учись думать в Brainfuck. Это сильно отличается от всего остального. Читайте и пишите, и переписывайте, и переписывайте множество программ для мозга. Язык не дает вам много работы, поэтому важно использовать то, что он дает вам гибко и эффективно. Не позволяйте никаким абстракциям встать между вами и языком - зайдите туда и боритесь с этим.

  • Получите очень удобно с неразрушающим контролем потока. Чтобы выйти из цикла принятия решения, вместо того, чтобы обнулять начальную ячейку, копируя ее в другое место и затем копируя ее обратно после выхода из цикла, часто лучше переместить указатель на уже существующий ноль поблизости. Да, это означает, что указатель будет находиться в разных местах в зависимости от того, проходили ли вы через цикл, но это также означает, что эти места, вероятно, имеют различные расположения соседних нулей и ненулевых элементов, которые можно использовать для повторной синхронизации расположения указателя с помощью другого цикла. Эта техника является фундаментальной для хорошего программирования Brainfuck, и различные ее формы будут постоянно полезны.

  • Это и тот факт, что каждый >или <расходы означают, что детали расположения памяти важны. Попробуйте столько вариантов вашего макета, сколько у вас есть терпение. И помните, ваш макет памяти не должен быть жестким отображением данных в местах. Это может измениться в процессе исполнения.

  • В более широком масштабе рассмотрите и даже попробуйте реализовать множество различных алгоритмов. Изначально не будет очевидно, какой именно алгоритм будет наилучшим; может даже не быть очевидным, какой базовый подход будет наилучшим, и он, вероятно, будет отличаться от того, что было бы лучше всего на обычном языке.

  • Если вы имеете дело с большими данными или данными переменного размера, посмотрите, есть ли какой-нибудь способ, которым вы можете иметь дело с ними локально, без необходимости отслеживать, насколько они велики или ваше числовое местоположение в них.

  • Одни и те же данные могут быть двумя разными вещами. (Чаще всего это число или символ, а также ненулевой позиционный маркер. Но см. Random.b , где счетчик битов удваивается как значение одной ячейки клеточного автомата.)

  • Один и тот же код может делать две разные вещи, и намного проще сделать это на языке, где код такой же общий, как и <+<. Будьте внимательны к таким возможностям. На самом деле, вы можете иногда замечать, даже в том, что кажется хорошо написанной программой, что есть небольшие фрагменты, которые можно полностью удалить, ничего не добавить, и что, случайно, все равно будет работать безупречно.

  • В большинстве языков вы часто используете компилятор или интерпретатор для проверки поведения вашей программы. Язык Brainfuck требует большего концептуального контроля; если вам нужен компилятор, чтобы рассказать вам о том, что делает ваша программа, у вас недостаточно четкого понимания вашей программы, и вам, вероятно, нужно еще на нее взглянуть - по крайней мере, если вы хотите иметь достаточно четкое изображение концептуальный ореол подобных программ, чтобы быть хорошим в гольфе. С практикой вы будете выпускать дюжину версий вашей программы, прежде чем пытаться ее запустить, и к этому моменту вы будете на 95% уверены, что ваша самая короткая версия будет работать правильно.

  • Удачи! Очень немногие люди стараются писать Brainfuck кратко, но я думаю, что это единственный способ, которым язык может оправдать постоянное внимание - как потрясающе малоизвестная форма искусства.

Даниэль Кристофани
источник
3
Прочитав это, я хочу попробовать программирование в Brainfuck сейчас ..
Claudiu
Черт возьми, думал, я узнал твое имя. Большой поклонник твоих программ мозгового штурма, особенно твоя супер-короткая интерпретация!
Джо Кинг,
«Вы можете иногда замечать ... что есть небольшие порции, которые можно полностью удалить, но ничего не добавить, и это, случайно, все равно будет работать безупречно». Это так верно, особенно для начинающего. Насколько я помню, я когда-либо писал только один ответ в BF, но его история изменений содержит, по крайней мере, 3 случая, когда я играл с программой и случайно сказал: «Эй, программа все еще работает, если я удаляю этот бит! "
ETHproductions
«Испытайте столько вариантов вашего макета, сколько у вас есть терпения», или можете грубой силой : P
Esolanging Fruit
9

Несколько советов здесь:

Константы:

Страница константы Esolangs' имеет чрезвычайно полезный список самых коротких путей для создания ценности конкретных. Я нахожу себя просматривающим эту страницу по крайней мере дважды для каждой программы.

Начало всего:

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

Это устанавливает ленту в формате 3 * n ^ 2, которая выглядит как

3 6 12 24 48 96 192 128 0 0 '

Почему это так важно?

Давайте спустимся по списку:

  • 3 и 6 скучные
  • 12: близко к 10 (перевод строки) или 13 (возврат каретки). Может также использоваться для счетчика на 0-9
  • 24: близко к 26, количество букв в алфавите
  • 48: ASCII для 0
  • 96: близко к 97, ASCII для a
  • 196 и 128: 196-128 = 64, близко к 65, ASCII для A.

Из этого одного алгоритма мы находимся в начале практически каждой последовательности в диапазоне ASCII, вместе со счетчиком для каждой и новой строкой в ​​легкодоступном месте.

Практический пример:

Печать всех прописных и строчных букв и цифр.

С алгоритмом:

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

Без:

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

Мы тратим большую часть байтов, просто инициализируя ленту во втором примере. Отчасти это компенсируется дополнительными движениями в первом примере, но этот метод явно имеет преимущество.

Пара других интересных алгоритмов в том же духе:

3 * 2 ^ п + 1:

+++[[<+>>++<-]+>]
Tape: 4 7 13 25 49 65 197 129 1 0'

Это смещает значения на 1, что выполняет несколько вещей. 12 делает возврат каретки, 64 - фактическое начало прописного алфавита, а 24 ближе к 26.

2 ^ п:

+[[<+>>++<-]>]
Tape: 1 2 4 8 16 32 64 128

Поскольку 64 подходит для заглавных букв, 32 - это ASCII для пробела, а 128 можно использовать в качестве счетчика для 26 (130/5 = 26). Это может сохранить байты в определенных ситуациях, когда цифры и строчные буквы не нужны.

Выберите реализацию, которая подходит для вопроса:

  • Отрицательные ячейки почти всегда полезны, и нет причин избегать их (если это не изменит ваш счет)
  • Почти то же самое с обтеканием ячеек, тем более что многие константы используют обтекание.
  • Произвольные размеры ячеек полезны для бесконечных математических последовательностей, таких как бесконечное вычисление последовательности Фибоначчи ( +[[-<+>>+>+<<]>]) или обработка больших / отрицательных чисел. Недостатком является то, что некоторые распространенные методы, такие как [-]и на [->+<]которые нельзя положиться, на случай, если число отрицательное.
  • EOF как 0, -1 или без изменений. 0 обычно предпочтительнее, так как вы можете перебрать весь ввод без дополнительных проверок. -1 полезно при зацикливании структур массива. Я еще не нашел применение без изменений :(.

Следите за тем, что происходит:

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

В любой момент мой код полон комментариев к каждой строке, которая выглядит следующим образом:

*0 *dat a_1 ?  0' !0 0*
 or
*0 *dat 0' ap1 0  !0 0*

Некоторый дополнительный совет должен назначить символам специальные значения. В приведенном выше примере, 'где указатель, *означает повторение в этом направлении, ?означает ячейку с неизвестным значением, !0означает ненулевую ячейку, _является заменой -и pзаменой +. orподразумевает, что лента может выглядеть как любое из представлений и должна обрабатываться как таковая.

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

Джо Кинг
источник
5

Мой главный совет - не надо.

Хорошо, хорошо, вы хотите что-то более полезное, чем это. BF уже очень лаконичен, но то, что вас действительно убивает, - это арифметика, которую нужно делать в унарном виде. Стоит прочитать на странице констант в Esolang, чтобы точно определить, как эффективно писать большие числа, и использовать обертку, где это возможно.

Доступ к памяти тоже очень дорогой. Поскольку вы читаете с кассеты, вам нужно помнить, куда движется ваша голова в любой момент времени. В отличие от других языков , где вы можете просто написать a, b, cв БФ вы должны явно переместить голову некоторое количество байт влево или вправо, так что вы должны помнить о том, где вы храните что. Я уверен, что оптимальной организацией вашей памяти является NP-сложная задача, так что удачи вам в этом.

ymbirtt
источник
5

В этом ответе я собираюсь сослаться на определенную ячейку на ленте много раз. Неважно, какая это ячейка, но это одна и та же ячейка на протяжении всего ответа. Для целей этого поста я назову эту ячейку "Тодд".

При попытке установить ячейку в постоянное значение иногда возникает необходимость не завершить ее немедленно. Например, допустим, что вы хотите, чтобы в Todd содержалось 30. Позже в вашем коде (который может изменить значение Todd, но никогда не читает его) вы возвращаетесь к Todd. Если значение Тодда равно 0, программа завершается. В противном случае значение Тодда печатается навсегда.

Согласно странице esolangs.org о константах мозгового мошенничества (которая, возможно, сама по себе может быть предметом подсказки!), Самый короткий способ получить 30 - это >+[--[<]>>+<-]>+. Это >ведение только для того, чтобы гарантировать, что ничего слева от указателя не будет изменено, но в этом случае мы будем считать, что нам это безразлично, и отбросим его. Используя этот код, ваш код будет выглядеть примерно так:

+[--[<]>>+<-]>+(MISC. CODE)(GO TO TODD)[.]

Вы можете думать о первом фрагменте кода следующим образом:

(SET TODD TO 30)(MISC. CODE)(GO TO TODD)[.]

Но помните , последние два символа в этом фрагменте: >+. Это так же правильно думать об этом так:

(SET TODD TO 29)(GO TO TODD)(ADD 1 TO TODD)(MISC. CODE)(GO TO TODD)[.]

Обратите внимание, что вы (GO TO TODD)дважды! Вместо этого вы можете написать свой код следующим образом:

(SET TODD TO 29)(MISC. CODE)(GO TO TODD)(ADD 1 TO TODD)[.]
+[--[<]>>+<-](MISC. CODE)(GO TO TODD)+[.]

Предполагая, что количество байт, которое требуется, (GO TO TODD)является тем же самым прежде, на один ход меньше == на один байт меньше! Иногда тот факт, что ваша стартовая позиция изменилась, лишает вас этой выгоды, но не всегда.

undergroundmonorail
источник
0

Маленький совет для проблем без участия. Вы можете использовать ,вместо [-], если вам нужно быстро очистить ячейку, так как большинство интерпретаторов (включая TIO.run) установят для содержимого ячейки значение EOF, равное нулю. Это делает программы немного непереносимыми, но кого это волнует в коде гольф?

Кшиштоф Шевчик
источник