Я представляю вам, первые 3% гексагонии самоинтерпретации ...
|./...\..._..>}{<$}=<;>'<..../;<_'\{*46\..8._~;/;{{;<..|M..'{.>{{=.<.).|.."~....._.>(=</.\=\'$/}{<}.\../>../..._>../_....@/{$|....>...</..~\.>,<$/'";{}({/>-'(<\=&\><${~-"~<$)<....'.>=&'*){=&')&}\'\'2"'23}}_}&<_3.>.'*)'-<>{=/{\*={(&)'){\$<....={\>}}}\&32'-<=._.)}=)+'_+'&<
Попробуйте онлайн! Вы также можете запустить его на себя, но это займет около 5-10 секунд.
В принципе, это может вписаться в длину стороны 9 (для оценки 217 или меньше), потому что для этого используются только 201 команда, а для версии без гольфа, которую я написал первым (для стороны 30), требовалось всего 178 команд. Тем не менее, я уверен, что на то, чтобы все привести в порядок, понадобится целая вечность, поэтому я не уверен, действительно ли я попытаюсь это сделать.
Также должно быть возможно сыграть это немного в размере 10, избегая использования последних одного или двух рядов, так что завершающие no-ops могут быть опущены, но это потребовало бы существенной перезаписи, как одного из первых путей присоединения использует нижний левый угол.
объяснение
Давайте начнем с развертывания кода и аннотирования путей потока управления:
Это все еще довольно грязно, так что вот та же диаграмма для «неопрятного» кода, который я написал первым (на самом деле, это длина стороны 20, и первоначально я написал код на стороне длины 30, но это было настолько редко, что не улучшает читаемость, поэтому я немного его сжал, чтобы сделать размер более разумным):
Нажмите для увеличения версии.
Цвета точно такие же, за исключением нескольких очень мелких деталей, команды неуправляемого потока также точно такие же. Итак, я объясню, как это работает, на основе версии без гольфа, и если вы действительно хотите знать, как работает гольф, вы можете проверить, какие части соответствуют каким в большем шестиугольнике. (Единственный улов заключается в том, что код для игры в гольф начинается с зеркала, так что фактический код начинается в правом углу и идет влево.)
Основной алгоритм почти идентичен моему ответу CJam . Есть два отличия:
- Вместо того, чтобы решать уравнение центрированных гексагональных чисел, я просто вычисляю последовательные центрированные гексагональные числа до тех пор, пока одно из них не станет равным или больше, чем длина ввода. Это потому, что у гексагонии нет простого способа вычисления квадратного корня.
- Вместо того, чтобы сразу заполнять ввод no-ops, я проверяю позже, исчерпал ли я уже введенные команды во вводе, и
.
вместо этого печатаю a .
Это означает, что основная идея сводится к:
- Чтение и сохранение входной строки при расчете ее длины.
- Найдите наименьшую длину стороны
N
(и соответствующее центрированное шестиугольное число hex(N)
), которая может содержать весь ввод.
- Вычислить диаметр
2N-1
.
- Для каждой строки вычислите отступ и количество ячеек (которые суммируются
2N-1
). Напечатайте отступ, напечатайте ячейки (используя, .
если ввод уже исчерпан), напечатайте перевод строки.
Обратите внимание на то, что есть только no-ops, поэтому фактический код начинается в левом углу (тот $
, который перепрыгивает через >
, поэтому мы действительно начинаем с ,
темно-серого пути).
Вот начальная сетка памяти:
Таким образом, указатель памяти начинается с обозначенного ребром ввода , указывая на север. ,
читает байт из STDIN или a, -1
если мы ударили EOF в этот край. Следовательно, <
сразу после является условием того, прочитали ли мы все входные данные. Давайте пока будем оставаться в цикле ввода. Следующий код, который мы выполняем
{&32'-
Это записывает 32 в пространство , помеченное ребром , а затем вычитает его из входного значения в ребро, помеченное как дифференциал . Обратите внимание, что это никогда не может быть отрицательным, потому что мы гарантируем, что ввод содержит только печатный ASCII. Это будет ноль, когда ввод был пробел. (Как указывает Тимви, это все равно будет работать, если на входе могут содержаться переводы строк или табуляции, но оно также удалит все другие непечатаемые символы с кодами символов менее 32). В этом случае <
указатель инструкций (IP) отклоняется слева и светло-серый путь взят. Этот путь просто сбрасывает позицию MP с помощью {=
и затем читает следующий символ - таким образом, пробелы пропускаются. В противном случае, если символ не был пробелом, мы выполняем
=}}})&'+'+)=}
Это сначала перемещается вокруг шестиугольника через край длины до его противоположного края дифференциала , с =}}}
. Затем он копирует значение из напротив длины кромки в длину края, а также увеличивает его с )&'+'+)
. Через секунду мы увидим, почему это имеет смысл. Наконец, мы перемещаем новое ребро с помощью =}
:
(Конкретные значения ребер взяты из последнего контрольного примера, приведенного в тесте.) В этот момент цикл повторяется, но со всем смещением на один шестиугольник на северо-восток. Итак, после прочтения другого символа, мы получаем это:
Теперь вы можете видеть, что мы постепенно записываем ввод (минус пробелы) вдоль северо-восточной диагонали, с символами на каждом другом ребре, а длина до этого символа сохраняется параллельно длине, обозначенной как ребро .
Когда мы закончим с циклом ввода, память будет выглядеть так (где я уже обозначил несколько новых ребер для следующей части):
Это %
последний символ, который мы прочитали, 29
это количество непробельных символов, которые мы прочитали. Теперь мы хотим найти длину стороны шестиугольника. Во-первых, есть некоторый линейный код инициализации в темно-зеленом / сером пути:
=&''3{
Здесь, =&
копирует длину (в нашем примере 29) в ребро, помеченное как длина . Затем ''3
перемещается к ребру с меткой 3 и устанавливает его значение 3
(которое нам просто нужно в качестве константы в вычислении). Наконец {
движется к краю с надписью N (N-1) .
Теперь мы входим в синюю петлю. Этот цикл увеличивается N
(сохраняется в ячейке с меткой N ), а затем вычисляет его центрированное шестиугольное число и вычитает его из входной длины. Линейный код, который делает это:
{)')&({=*'*)'-
Здесь, {)
перемещается и приращения N . ')&(
перемещается к краю с меткой N-1 , копирует N
туда и уменьшает его. {=*
вычисляет их произведение в N (N-1) . '*)
умножает это на константу 3
и увеличивает результат на ребро с меткой hex (N) . Как и ожидалось, это шестое центрированное шестиугольное число. Наконец '-
вычисляет разницу между этим и длиной ввода. Если результат положительный, длина стороны еще не достаточно велика, и цикл повторяется (где }}
MP возвращается к краю, обозначенному N (N-1) ).
Как только длина стороны будет достаточно большой, разница будет нулевой или отрицательной, и мы получим это:
Во-первых, теперь есть действительно длинный линейный зеленый путь, который выполняет некоторую необходимую инициализацию для цикла вывода:
{=&}}}32'"2'=&'*){=&')&}}
В {=&
начинается с копирования результата в дифф края в длину края, потому что мы потом нужно что - то неположительны там. }}}32
записывает 32 в край помеченного пространства . '"2
записывает константу 2 в немаркированный край над diff . '=&
копирует N-1
во второй край с той же меткой. '*)
умножает его на 2 и увеличивает его так, чтобы мы получили правильное значение в ребре с меткой 2N-1 в верхней части. Это диаметр шестиугольника. {=&')&
копирует диаметр в другой край, помеченный 2N-1 . Наконец }}
возвращается к краю с надписью 2N-1 в верхней части.
Давайте перемаркируем края:
Ребро, на котором мы сейчас находимся (который все еще содержит диаметр шестиугольника), будет использоваться для итерации по линиям вывода. Ребро с меткой отступ вычислит, сколько пробелов необходимо в текущей строке. Ячейки, помеченные ребром, будут использоваться для перебора количества ячеек в текущей строке.
Мы сейчас на розовом пути, который вычисляет отступ . ('-
уменьшает значение итератора строк и вычитает его из N-1 (в ребро с отступом ). Короткая сине-серая ветвь в коде просто вычисляет модуль результата ( ~
отрицает значение, если оно отрицательное или ноль, и ничего не происходит, если оно положительное). Остальная часть розового пути - это то, "-~{
что вычитает отступ от диаметра до края ячеек, а затем перемещается обратно к краю отступа .
Грязно-желтый путь теперь печатает отступ. Содержимое цикла действительно просто
'";{}(
Где '"
перемещается к краю пробела , ;
печатает его, {}
возвращается к отступу и (
уменьшает его.
Когда мы закончим с этим (второй) темно-серый путь ищет следующий символ для печати. В =}
перемещается в положение (что означает, на клетки края, указывающие на юг). Тогда у нас есть очень узкая петля, {}
которая просто перемещается вниз по двум краям в направлении юго-запада, пока мы не достигнем конца сохраненной строки:
Заметьте, что я пометил один край там EOF? , Как только мы обработаем этот символ, мы сделаем это ребро отрицательным, чтобы {}
цикл завершился здесь вместо следующей итерации:
В коде мы находимся в конце темно-серого пути, где '
возвращаемся на один шаг назад к вводимому символу. Если ситуация является одной из двух последних диаграмм (то есть, есть еще один символ из ввода, который мы еще не распечатали), то мы выбираем зеленый путь (нижний, для людей, которые плохо разбираются в зеленом и синий). Это довольно просто: ;
печатает сам символ. '
перемещается к соответствующему краю пробела, который по-прежнему содержит 32 ранее и ;
печатает этот пробел. Тогда {~
делает наш EOF? отрицательный для следующей итерации, '
перемещает на шаг назад, чтобы мы могли вернуться к северо-западному концу строки с помощью еще одного сложного }{
цикла. Который заканчивается на длинуячейка (неположительная ниже гекса (N) . Наконец, }
возвращается к краю ячейки .
Если мы уже исчерпали ввод, тогда цикл, который ищет EOF? на самом деле закончится здесь:
В этом случае '
перемещаемся в ячейку длины , и вместо этого мы берем голубой (верхний) путь, который печатает неоперативный код. Код в этой ветке линейный:
{*46;{{;{{=
{*46;
Пишет 46 в край не меченый не-оп и печатает его (т.е. периода). Затем {{;
перемещается к краю пространства и печатает это. В {{=
двигается обратно к клеткам краям для следующей итерации.
В этот момент пути объединяются и (
уменьшают границу ячеек . Если итератор еще не равен нулю, мы возьмем светло-серый путь, который просто меняет направление MP =
и затем ищет следующий символ для печати.
В противном случае мы достигли конца текущей строки, и вместо этого IP выберет фиолетовый путь. Вот как выглядит сетка памяти в этой точке:
Фиолетовый путь содержит это:
=M8;~'"=
=
Снова меняет направление МП. M8
устанавливает для его значений значение 778
(потому что код символа M
is 77
и цифры будут добавлены к текущему значению). Это происходит 10 (mod 256)
, поэтому, когда мы печатаем его ;
, мы получаем перевод строки. Затем ~
снова делает край отрицательным, '"
возвращается к краю линий и снова =
меняет MP.
Теперь, если край линий равен нулю, мы закончили. IP выберет (очень короткий) красный путь, где @
завершает программу. В противном случае мы продолжаем идти по фиолетовому пути, который возвращается в розовый, чтобы напечатать еще одну строку.
Блок-схемы управления, созданные с помощью HexagonyColorer Тимви . Диаграммы памяти, созданные с помощью визуального отладчика в его Esoteric IDE .
abc`defg
что на самом деле станет pastebin.com/ZrdJmHiR