Самый быстрый Mini-Flak Quine


26

Мини-Flak является подмножеством Brain-Flak языка, где <>, <...>и []операции неразрешенным. Строго говоря, оно не должно соответствовать следующему регулярному выражению:

.*(<|>|\[])

Mini-Flak - это наименьшее из известных подмножеств Тьюринга Brain-Flak.


Некоторое время назад мне удалось создать Quine в Mini-Flak , но он был слишком медленным, чтобы бежать во время существования вселенной.

Поэтому моя задача для вас - сделать Куайн быстрее.


счет

Чтобы оценить ваш код, поставьте @cyфлаг в конце вашего кода и запустите его в интерпретаторе Ruby ( попробуйте онлайн, используя интерпретатор ruby), используя -dфлаг. Ваша оценка должна быть напечатана в STDERR следующим образом:

@cy <score>

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

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

Подсчет количества циклов не очень сложен. Количество циклов эквивалентно 2-кратному числу выполненных монад плюс число выполненных нилад. Это то же самое, что заменить каждую ниладу одним символом и подсчитать общее количество выполненных символов.

Пример оценки

  • (()()()) оценка 5, потому что она имеет 1 монаду и 3 нилады.

  • (()()()){({}[()])} 29 баллов, потому что первая часть та же, что и раньше, и 5 баллов, в то время как цикл содержит 6 монад и 2 нилады, набрав 8 баллов. Цикл запускается 3 раза, поэтому мы считаем его 3 раза 1*5 + 3*8 = 29


Требования

Ваша программа должна ...

  • Быть минимум 2 байта

  • Напечатайте его исходный код при выполнении в Brain-Flak с использованием -Aфлага

  • Не соответствует регулярному выражению .*(<|>|\[])


подсказки

  • Кран-Flak переводчик категорически быстрее , чем интерпретатор рубина , но не хватает некоторых функций. Я бы порекомендовал сначала протестировать ваш код с помощью Crane-Flak, а затем оценить его в интерпретаторе ruby, когда вы знаете, что он работает. Я также настоятельно рекомендую не запускать вашу программу в TIO. TIO не только медленнее, чем настольный интерпретатор, но также истекает через минуту. Было бы очень впечатляюще, если бы кто-то смог набрать достаточно низкий балл, чтобы запустить свою программу до истечения времени ожидания TIO.

  • [(...)]{}и (...)[{}]работают так же, как, <...>но не нарушают требование ограниченного источника

  • Вы можете проверить Brain-Flak и Mini-Flak Quines, если хотите получить представление о том, как решить эту проблему.


1
«текущий лучший» -> «только текущий»
HyperNeutrino

Ответы:


33

Мини-Флак, 6851113 циклов

Программа (буквально)

Я знаю, что большинство людей вряд ли ожидают, что в мини-Flak-квине будут использоваться непечатаемые символы и даже многобайтовые символы (что делает кодировку актуальной). Тем не менее, эта квинна делает, и непечатаемые, в сочетании с размером квинс (93919 символов, закодированных как 102646 байт UTF-8), довольно затрудняют размещение программы в этом посте.

Тем не менее, программа очень повторяющаяся и, как таковая, сжимается очень хорошо. Так что вся программа доступна буквально из Stack Exchange, xxdза gzipразворачиваемой таблицей скрыта шестнадцатеричная двусторонняя шестнадцатеричная версия полной сжатой версии:

(Да, это настолько повторяется, что вы даже можете увидеть повторы после того, как они были сжаты).

В вопросе говорится: «Я бы также настоятельно рекомендовал не запускать вашу программу в TIO. TIO не только медленнее, чем настольный интерпретатор, но также истекает через минуту. Было бы очень впечатляющим, если бы кто-то смог набрать достаточно низкий балл для запуска их программа до истечения времени ожидания TIO. " Я могу это сделать! Работа с TIO на интерпретаторе Ruby занимает около 20 секунд: попробуйте онлайн!

Программа (читабельно)

Теперь я дал версию программы, которую могут читать компьютеры, давайте попробуем версию, которую могут читать люди. Я преобразовал байты, составляющие квинту, в кодовую страницу 437 (если у них установлен старший бит) или в управляющие изображения Unicode (если это управляющие коды ASCII), добавил пробел (любой ранее существующий пробел был преобразован в управляющие картинки). ), кодируется по длине прогона с использованием синтаксиса «string×length»и отбрасываются некоторые биты с большими объемами данных:

␠
(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                («()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
                … much more data encoded the same way …
                («()×117»(«()×115»(«()×117»
                «000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
                )[({})(
                    ([({})]({}{}))
                    {
                        ((()[()]))
                    }{}
                    {
                        {
                            ({}(((({}())[()])))[{}()])
                        }{}
                        (({}))
                        ((()[()]))
                    }{}
                )]{}
                %Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
                ,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

(«Почти 241» - потому что в 241-й копии отсутствует завершающий знак ', но в остальном он идентичен другим 240.)

объяснение

О комментариях

Первое, что нужно объяснить: что случилось с непечатными символами и прочим мусором, который не является командами Mini-Flak? Вы можете подумать, что добавление комментариев к квине просто усложняет ситуацию, но это соревнование в скорости (а не соревнование в размере), означающее, что комментарии не влияют на скорость программы. Тем временем Brain-Flak и, следовательно, Mini-Flak просто сбрасывают содержимое стека на стандартный вывод; если бы вы должны были убедиться, что стек содержит толькоСимволы, составляющие команды вашей программы, вам придется тратить циклы на очистку стека. Как таковой, Brain-Flak игнорирует большинство символов, так что пока мы гарантируем, что элементы стека нежелательной почты не являются действительными командами Brain-Flak (делая это полиглотом Brain-Flak / Mini-Flak), и не являются отрицательными или внешними диапазон Юникода, мы можем просто оставить их в стеке, разрешить их вывод и поместить один и тот же символ в нашу программу в то же место, чтобы сохранить свойство quine.

Есть один особенно важный способ, которым мы можем воспользоваться этим. Quine работает с использованием длинной строки данных, и в основном все выходные данные из Quine создаются путем форматирования строки данных различными способами. Есть только одна строка данных, несмотря на то, что программа состоит из нескольких частей; поэтому мы должны иметь возможность использовать одну и ту же строку данных для печати разных частей программы. Трюк «ненужные данные не имеет значения» позволяет нам сделать это очень простым способом; мы сохраняем символы, составляющие программу, в строке данных, добавляя или вычитая значение в или из их кода ASCII. В частности, символы, составляющие начало программы, сохраняются как их код ASCII + 4, символы, составляющие раздел, который повторяется почти 241 раз, как их код ASCII - 4,каждый символ строки данных со смещением; если, например, мы распечатаем его с 4, добавленными к каждому символьному коду, мы получим один повтор повторяющегося раздела с некоторыми комментариями до и после. (Эти комментарии - просто другие разделы программы, с кодами символов, смещенными так, что они не образуют никаких допустимых команд Brain-Flak, потому что было добавлено неправильное смещение. Мы должны уклоняться от команд Brain-Flak, а не только от Mini- Flak команды, чтобы избежать нарушения части вопроса с ; выбор смещений был разработан, чтобы гарантировать это.)

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

Структура программы

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

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

  • Чтобы создать outro, отформатируйте строку данных со смещением +8
  • Чтобы создать форматировщик строки данных, отформатируйте строку данных со смещением +4, 241 раз
  • Чтобы создать строку данных, отформатируйте строку данных с помощью ее кодирования в исходный формат.
  • Для создания вступления отформатируйте строку данных со смещением -4

Поэтому все, что нам нужно сделать, это посмотреть, как работают эти части программы.

Строка данных

(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …

Нам нужна простая кодировка для строки данных, поскольку мы должны иметь возможность изменить кодировку в коде Mini-Flak. Вы не можете стать намного проще, чем это!

Ключевая идея этого лозунга (кроме трюка с комментариями) заключается в том, чтобы заметить, что существует только одно место, в котором мы можем хранить большие объемы данных: «суммы возвращаемых значений команд» в пределах различных уровней вложенности исходного кода программы. (Это обычно называют третьим стеком, хотя у Mini-Flak нет второго стека, поэтому «рабочий стек», вероятно, является лучшим именем в контексте Mini-Flak.) Другими возможностями хранения данных будет основной / первый стек (который не работает потому что именно туда должны идти наши выходные данные, и мы не можем удаленно перемещать выходные данные за пределы хранилища) и кодировать их в bignum в одном элементе стека (что не подходит для этой проблемы, поскольку для этого требуется экспоненциальное время извлечь из него данные); когда вы их устраняете, рабочий стек остается единственным местом.

Чтобы «хранить» данные в этом стеке, мы используем несбалансированные команды (в данном случае, первую половину (…)команды), которые позже будут сбалансированы в формататоре строк данных. Каждый раз, когда мы закрываем одну из этих команд в модуле форматирования, она выталкивает сумму данных, взятых из строки данных, и возвращает значения всех команд на этом уровне вложенности в модуле форматирования; мы можем гарантировать, что последние добавляются к нулю, поэтому форматировщик просто видит отдельные значения, взятые из строки данных.

Формат очень прост: (за ним следуют n копий (), где n - это число, которое мы хотим сохранить. (Обратите внимание, что это означает, что мы можем хранить только неотрицательные числа, и последний элемент строки данных должен быть положительным.)

Один немного неинтуитивный момент в отношении строки данных - это порядок, в котором она находится. «Начало» строки данных - это конец ближе к началу программы, то есть самый внешний уровень вложенности; эта часть форматируется последней (поскольку форматировщик работает от самого внутреннего до самого внешнего уровня вложенности). Однако, несмотря на то, что форматируется последним, он печатается первым, потому что значения, помещенные в стек первым, печатаются последними интерпретатором Mini-Flak. Тот же принцип применяется к программе в целом; нам нужно сначала отформатировать outro, затем форматер строки данных, затем строку данных, затем intro, то есть в обратном порядке, в котором они хранятся в программе.

Форматирование строки данных

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

Модуль форматирования строки данных состоит из 241 раздела, каждый из которых имеет идентичный код (один раздел имеет незначительно отличающийся комментарий), каждый из которых форматирует один конкретный символ строки данных. (Мы не могли использовать цикл здесь: нам нужен несбалансированный )для чтения строки данных путем сопоставления его несбалансированным (, и мы не можем поместить один из них в {…}цикл, единственную форму цикла, которая существует. Поэтому вместо этого мы " разверните «форматтер» и просто заставьте вступление / выход для вывода строки данных со смещением форматера 241 раз.)

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

Самая внешняя часть элемента форматирования считывает один элемент строки данных; простота кодирования строки данных приводит к небольшой сложности при ее чтении. Мы начинаем с закрытия несопоставленного значения (…)в строке данных, а затем отрицаем ( […]) два значения: данные, которые мы только что прочитали из строки данных ( ({})), и возвращаемое значение остальной части программы. Мы копируем возвращаемое значение остальной части элемента форматирования с помощью (…)и добавляем копию в отмененную версию с {}. Конечным результатом является то, что возвращаемое значение элемента строки данных и элемента форматирования вместе представляет собой элемент данных минус элемент данных минус возвращаемое значение плюс возвращаемое значение или 0; это необходимо, чтобы следующий элемент строки данных выдал правильное значение.

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

Модуль форматирования использует верхний элемент стека, чтобы узнать, в каком режиме он находится (0 = формат в форматировании строки данных, любое другое значение = смещение для вывода). Тем не менее, просто прочитав строку данных, элемент данных находится поверх формата в стеке, и мы хотим, чтобы они были наоборот. Этот код является более коротким вариантом кода обмена Brain-Flak, взяв a от b до b выше a  +  b ; он не только короче, но и (в данном конкретном случае) более полезен, поскольку побочный эффект от добавления b к a не вызывает проблем, когда b равно 0, а когда b не равно 0, он выполняет расчет смещения для нас.

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

Brain-Flak имеет только одну структуру потока управления, поэтому, если нам нужно что-то кроме whileцикла, это займет немного работы. Это структура "отрицания"; если в стеке есть 0, он удаляет его, в противном случае он помещает 0 в верхнюю часть стека. (Это работает довольно просто: до тех пор, пока на стеке нет 0, дважды нажмите 1 - 1 в стек; когда вы закончите, вытолкните верхний элемент стека.)

Как показано здесь, можно поместить код в отрицательную структуру. Код будет выполняться только в том случае, если вершина стека отлична от нуля; так что если у нас есть два отрицающих структуры, предполагая , что два верхних элемента стека не как ноль, они компенсируют друг друга, но любой код внутри первой структуры будет работать только тогда , когда элемент верхнего стека был отличен от нуля, и код внутри вторая структура будет работать, только если верхний элемент стека был равен нулю. Другими словами, это эквивалент оператора if-then-else.

В предложении «then», которое выполняется, если формат ненулевой, нам фактически нечего делать; нам нужно поместить данные + смещение в основной стек (чтобы его можно было выводить в конце программы), но оно уже есть. Таким образом, мы имеем дело только со случаем кодирования элемента строки данных в исходной форме.

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

Вот как мы это делаем. {({}( … )[{}()])}{}Структура должна быть знакома как цикл с определенным числом итераций (который работает путем перемещения счетчика цикла к рабочей стеке и удерживая ее там, она будет в безопасности от любого другого кода, так как доступ к рабочей стеке привязан к уровень вложенности программы). Тело цикла is ((({}())[()])), которое делает три копии верхнего стекового элемента и добавляет 1 к нижнему. Другими словами, он превращает 40 на вершине стека в 40 выше 40 выше 41, или рассматривается как ASCII, (в ((); работает этот раз будет делать (в (()в (()()в (()()()и так далее, и , таким образом , представляет собой простой способ для создания нашей строки данных (при условии , что есть (на вершине стека уже).

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

%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'

Есть еще один интерес в форматере строк данных. Итак, в основном это всего лишь 4 сдвинутых вниз кодовых точки; однако этот апостроф в конце может показаться неуместным. '(кодовая точка 39) переместится в +(кодовая точка 43), которая не является командой Brain-Flak, так что вы, возможно, догадались, что она существует для какой-то другой цели.

Причина этого заключается в том, что форматировщик строки данных ожидает, что (в стеке уже есть (он нигде не содержит литерала 40). 'фактически находится в начале блока, который повторяется для создания средства форматирования строки данных, а не в конце, поэтому после того, как символы средства форматирования строки данных были помещены в стек (и код собирается перейти к печати строки данных сам) outro настраивает 39 на вершине стека в 40, готовый к форматтеру (самому работающему на этот раз форматеру, а не его представлению в источнике), чтобы использовать его. Вот почему у нас «почти 241» копий форматера; в первой копии отсутствует первый символ. И этот символ, апостроф, является одним из трех символов в строке данных, которые не соответствуют коду Mini-Flak где-то в программе; это просто как метод обеспечения константы.

Интро и аутро

(((()()()()){}))
{{}
    (({})[(()()()())])
    (({})(
        {{}{}((()[()]))}{}
        (((((((({})){}){}{})){}{}){}){}())
        {
            ({}(
                (␀␀!S␠su! … many more comment characters … oq␝qoqoq)
                …
            )[{}()])
        }{}
        {}({}())
    )[{}])
    (({})(()()()()){})
}{}{}␊

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

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

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

Есть и другие представления числа 8, которые не длиннее этого. Тем не менее, при работе с самым быстрым кодом, это определенно лучший вариант. С одной стороны, использование ()()()()происходит быстрее, чем, скажем, (()()){}потому что, несмотря на то, что оба имеют длину 8 символов, первый из них является циклом быстрее, поскольку (…)считается как 2 цикла, но ()только как один. Сохранение одного цикла незначительно по сравнению с гораздо большим соображением для , хотя: (и )имеют гораздо более низкие кодовые точки, чем {и }, поэтому генерация фрагмента данных для них будет намного быстрее (и фрагмент данных будет занимать меньше места в коде, слишком).

{{} … }{}{}

Основной цикл. Здесь не учитываются итерации (это whileцикл, а не forцикл, и для его завершения используется тест). Как только он выходит, мы отбрасываем два верхних элемента стека; верхний элемент - безвредный 0, но элемент ниже будет «форматом для использования на следующей итерации», который (будучи отрицательным смещением) является отрицательным числом, и если в стеке есть отрицательные числа, когда Mini -Flak программа выходит, интерпретатор падает, пытаясь их вывести.

Поскольку этот цикл использует явный тест, чтобы выйти из него, результат этого теста останется в стеке, поэтому мы отбрасываем его как первое, что мы делаем (его значение бесполезно).

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

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

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

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

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

Проверка, чтобы увидеть, равно ли смещение 4 (т.е. f  - 4 равно 0). Если это так, мы печатаем форматер строки данных, поэтому нам нужно выполнить строку данных и ее форматер 241 раз, а не один раз с этим смещением. Код довольно прост: если f  - 4 не равен нулю, замените f  - 4 и само 4 на пару нулей; тогда в любом случае, вытолкните верхний элемент стека. Теперь у нас в стеке число выше f , либо 4 (если мы хотим напечатать эту итерацию 241 раз), либо 0 (если мы хотим напечатать ее только один раз).

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

Это интересный вид константы Brain-Flak / Mini-Flak; длинная линия здесь представляет число 60. Вы можете быть смущены отсутствием (), которое обычно присутствует в константах Brain-Flak; это не обычное число, а церковное число, которое интерпретирует числа как операцию дублирования. Например, церковная цифра 60, показанная здесь, делает 60 копий своих входных данных и объединяет их все вместе в одно значение; в Brain-Flak единственное, что мы можем объединить, - это обычные числа, кроме того, поэтому мы в итоге добавляем 60 копий вершины стека и, таким образом, умножаем верх стека на 60.

В качестве примечания можно использовать искатель чисел Underload , который генерирует церковные цифры в синтаксисе Underload, чтобы найти соответствующее число и в Mini-Flak. Цифры недогрузки (отличные от нуля) используют операции «дублировать верхний элемент стека» :и «объединить два верхних элемента стека» *; обе эти операции существуют в Brain-Flak, так что вы просто переводите :в ), *до {}, {}добавляете a и добавляете достаточно (в начале, чтобы сбалансировать (это использует странное сочетание основного стека и рабочего стека, но это работает).

Этот конкретный фрагмент кода использует церковную цифру 60 (фактически, фрагмент «умножить на 60») вместе с приращением, чтобы сгенерировать выражение 60 x  + 1. Таким образом, если у нас было 4 из предыдущего шага, это дает нам значение 241, или если у нас было 0, мы просто получаем значение 1, т.е. это правильно вычисляет количество итераций, которые нам нужны.

Выбор 241 не случаен; это значение было выбрано равным а) приблизительной длине, на которой программа все равно закончится, и б) 1 более чем в 4 раза больше круглого числа. Круглые числа, в данном случае 60, имеют тенденцию иметь более короткие представления в виде церковных чисел, потому что у вас больше гибкости в копировании факторов. Позже программа содержит отступы, чтобы довести длину до 241 точно.

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

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

(␀␀!S␠su! … many more comment characters … oq␝qoqoq)

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

Более того, эти символы NUL в начале комментария явно не смещены ни от чего (даже разницы между +8 и -4 недостаточно, чтобы превратить (NUL в). Это чистое заполнение для доведения строки данных из 239 элементов до 241 элемента (что легко окупается: для генерации 1 против 239 потребуется гораздо больше двух байтов, чем 1 против 241 при расчете необходимого числа итераций). ). NUL был использован как символ заполнения, потому что он имеет наименьшую возможную кодовую точку (что делает исходный код для строки данных короче и, следовательно, быстрее выводить).

{}({}())

Удалите верхний элемент стека (формат, который мы используем), добавьте 1 к следующему (последний символ, который будет выведен, то есть первый символ, который будет напечатан, из раздела программы, который мы только что отформатировали). Нам больше не нужен старый формат (новый формат скрывается в рабочем стеке); и приращение в большинстве случаев безвредно и меняет 'на одном конце исходного представления форматера строки данных на ((что требуется в стеке при следующем запуске форматера для форматирования самой строки данных). Нам нужно преобразование, подобное этому в outro или intro, потому что принудительное начало каждого элемента форматирования строки данных (сделало бы его несколько более сложным (поскольку нам нужно было бы закрыть (и затем отменить его эффект позже), инам нужно как-то сгенерировать лишнее (где-нибудь, потому что у нас есть только почти 241 копия средства форматирования, а не все 241 (так что лучше всего, чтобы такой безобидный символ, как 'тот, который отсутствует).

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

Наконец, тест выхода из цикла. Текущая вершина основного стека - это формат, который нам нужен для следующей итерации (которая только что вернулась из рабочего стека). Это копирует это и добавляет 8 к копии; Полученное значение будет отброшено в следующий раз в цикле. Однако, если мы только что напечатали вступление, смещение было равно -4, поэтому смещение для «следующей итерации» будет равно -8; -8 + 8 равно 0, поэтому цикл будет выходить, а не продолжаться на итерации впоследствии.


16

128 673 515 циклов

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

объяснение

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


Этот блок кода имеет два требования.

  • Он должен принимать число и выводить только код символа для этого символа

  • В Brain-Flak должно быть легко воспроизводить таблицу поиска постепенно

Чтобы построить этот блок, я фактически использовал метод из моего доказательства того, что Miniflak завершен по Тьюрингу. Для каждого элемента есть блок кода, который выглядит следующим образом:

(({}[()])[(())]()){(([({}{})]{}))}{}{(([({}{}(%s))]{}))}{}

Это вычитает единицу из числа на вершине стека, и если ноль выталкивает %sданные под ним. Поскольку каждый фрагмент уменьшает размер на единицу, если вы начнете с n в стеке, вы получите n-ую точку отсчета.

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


Затем мы должны настроить машину, которая фактически переводит эту память в источник. Это состоит из 3 частей как таковых:

(([()]())())
{({}[(
  -Look up table-
 )]{})
 1. (({}[()])[(())]()){(([({}{})]{}))}{}{([({}{}(([{}]))(()()()()()))]{})}{}

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

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

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

Машина состоит из четырех частей, которые запускаются по порядку, начиная с 1 и заканчивая 3. Я пометил их в приведенном выше коде. Каждый раздел также использует один и тот же формат таблицы поиска, который я использую для кодирования. Это потому, что вся программа содержится в цикле, и мы не хотим запускать каждый раздел каждый раз, когда выполняем цикл, поэтому мы вставляем одну и ту же структуру RA и запрашиваем нужный раздел каждый раз.

1

Раздел 1 - это простой раздел настройки.

Программа сообщает первым запросам секцию 1 и данные 0. Элемент 0 не существует, поэтому вместо возврата этого значения он просто уменьшает запрос один раз для каждого элемента данных. Это полезно, потому что мы можем использовать результат для определения количества данных, которые станут важными в следующих разделах. Раздел 1 записывает количество данных путем отрицательного результата и запрашивает раздел 2 и последние данные. Единственная проблема в том, что мы не можем запросить раздел 2 напрямую. Так как остался еще один декремент, нам нужно запросить несуществующий раздел 5. На самом деле это будет иметь место каждый раз, когда мы запрашиваем раздел в другом разделе. Я буду игнорировать это в моем объяснении, однако, если вы ищете код, просто запомните 5 означает вернуться к разделу, а 4 означает запустить тот же раздел снова.

2

Раздел 2 декодирует данные в символы, которые составляют код после блока данных. Каждый раз он ожидает, что стек будет выглядеть так:

Previous query
Result of query
Number of data
Junk we shouldn't touch...

Он отображает каждый возможный результат (число от 1 до 6) на один из шести допустимых символов Miniflak ( (){}[]) и помещает его ниже числа данных с надписью «Хлам, который мы не должны трогать». Это дает нам стек, как:

Previous query
Number of data
Junk we shouldn't touch...

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

Next query
Number of data
Junk we shouldn't touch...

Если наш следующий запрос равен нулю, мы прочитали всю память, необходимую в разделе 3, поэтому мы снова добавляем количество данных в запрос и добавляем 4 к вершине стека, чтобы перейти к разделу 3. Если следующий запрос не равен нулю, мы положить 5 в стек, чтобы снова запустить раздел 2.

3

Раздел 3 создает блок данных, запрашивая нашу оперативную память точно так же, как раздел 3.

Для краткости я опущу большинство деталей того, как работает раздел 3. Он почти идентичен разделу 2, за исключением того, что вместо преобразования каждого элемента данных в один символ он переводит каждый в длинный фрагмент кода, представляющий его запись в ОЗУ. Когда раздел 3 закончен, он сообщает программе о выходе из цикла.


После того, как цикл был запущен, программе просто нужно нажать первый бит квин ([()]())(()()()()){({}[(. Я делаю это с помощью следующего кода, реализующего стандартные методы колмогоровской сложности.

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

Я надеюсь, что это было ясно. Пожалуйста, прокомментируйте, если вы что-то путаете.


О том, сколько времени это займет, чтобы бежать? Это раз на TIO.
Павел

@Pavel Я не запускаю его на TIO, потому что это будет невероятно медленно, я использую тот же интерпретатор, который использует TIO ( рубиновый ). Требуется приблизительно 20 минут, чтобы работать на старом сервере стойки, к которому у меня есть доступ. В Crain-Flak это занимает около 15 минут, но у Crain-Flak нет флагов отладки, поэтому я не могу забить его, не запустив его в интерпретаторе Ruby.
Пшеничный волшебник

@Pavel Я запустил его снова и рассчитал время. Это потребовалось 30m45.284sдля завершения на довольно низком уровне сервера (примерно эквивалент среднего современного рабочего стола) с использованием интерпретатора ruby.
Пшеница Волшебник
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.