Perl, 1116, 1124 байта, n = 3, оценка = 1124 ^ (2/3) или приблизительно 108,1.
Обновление : я теперь проверил, что это работает с n = 3 через грубую силу (что заняло пару дней); в такой сложной программе трудно вручную проверить радиационную стойкость (и в предыдущей версии я допустил одну ошибку, поэтому число байтов увеличилось). Конец обновления
Я рекомендую куда-нибудь перенаправить stderr, чтобы вы его не видели; эта программа выдает массу предупреждений о сомнительном синтаксисе, даже если вы не удаляете из него символы.
Возможно, что программа может быть сокращена. Работать над этим довольно болезненно, упрощая возможные микрооптимизации. Я в основном стремился максимально увеличить число удаляемых символов (потому что это действительно сложная часть программы) и относился к разрыву тай -кода с гольфом как к чему-то, к чему приятно стремиться, но как к чему-то, чего я бы не стал нелепые усилия по оптимизации (на том основании, что очень легко сломать радиационную стойкость случайно).
Программа
Примечание: буквальный управляющий _
символ (ASCII 31) находится непосредственно перед каждым из четырех вхождений -+
. Я не думаю, что он скопирован и вставлен в StackOverflow правильно, поэтому вам придется повторно добавить его перед запуском программы.
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
eval+<eval+<eval+<eval+(q(FoPqOlengthFoBBPP181XXVVVVJJJKKKNdoWchopJFtPDevalMODx4KNFrPIPA-MN-TUV-ZPINFsPIFoPqOI.Fo.IQNevalFoINevalIFsPZyI.Fr.IT-UPINsayDFtJqJFsKPZyPT-UFWrYrKD.DEEEEQDx6NsayDNDforB1..4YforB1..4NexitQNevalFo)=~y=A-Z=-+;-AZz-~=r)####>####>####>####>####>####>
;
;
;
;
Объяснение
Эта программа, совершенно ясно, состоит из четырех идентичных меньших программ, соединенных вместе. Основная идея заключается в том, что каждая копия программы будет проверять, была ли она повреждена слишком сильно для запуска или нет; если это так, он ничего не будет делать (кроме возможных предупреждений) и позволит запустить следующую копию; если его не было (т.е. не было удалений, или был удален символ, который не имеет значения для работы программы), он выполнит свою хитрость (распечатает полный исходный код программы; это правильный quine, с каждой частью, содержащей кодировку всего исходного кода), а затем завершите работу (не позволяя другим неповрежденным копиям снова распечатать исходный код и, таким образом, разрушить квинну, напечатав слишком много текста).
Каждая часть в свою очередь состоит из двух частей, которые являются функционально независимыми; внешняя обертка и некоторый внутренний код. Таким образом, мы можем рассмотреть их отдельно.
Внешняя обертка
Внешняя оболочка - это, в основном, eval<+eval<+eval< ... >####>####...>###
(плюс несколько точек с запятой и символов новой строки, цель которых должна быть достаточно очевидной; она заключается в том, чтобы гарантировать, что части программы останутся разделенными независимо от того, будут ли удалены некоторые точки с запятой или символы новой строки перед ними. ). Это может показаться довольно простым, но это не так во многих отношениях, и причина, по которой я выбрал Perl для этой задачи.
Во-первых, давайте посмотрим, как функционирует обертка в неповрежденной копии программы. eval
разбирает как встроенную функцию, которая принимает один аргумент. Поскольку ожидается спор, +
вот унарный +
(который будет очень знаком игрокам в Perl к настоящему моменту; они оказываются полезными на удивление часто). Мы все еще ожидаем аргумент (мы только что видели унарный оператор), поэтому <
следующий за ним интерпретируется как начало <>
оператора (который не принимает префиксного или постфиксного аргумента и, следовательно, может использоваться в позиции операнда).
<>
довольно странный оператор. Его обычно цель состоит в том, чтобы прочитать дескрипторы файлов, и вы поместите ссылку на файл имя в угловых скобках. Альтернативно, если выражение недопустимо в качестве имени дескриптора файла, оно выполняет глобализацию (в основном, тот же процесс, который оболочки UNIX используют для преобразования текста, введенного пользователем, в последовательность аргументов командной строки; фактически использовались гораздо более старые версии Perl). оболочка для этого, но в настоящее время Perl обрабатывает глобализацию внутренне). Следовательно, предполагаемое использование происходит по принципу <*.c>
, который обычно возвращает список вроде ("foo.c", "bar.c")
. В скалярном контексте (например, аргументeval
), он просто возвращает первую запись, которую находит при первом запуске (эквивалент первого аргумента), и возвращает другие записи в гипотетических будущих прогонах, которые никогда не произойдут.
Теперь оболочки часто обрабатывают аргументы командной строки; если вы дадите что-то вроде -r
без аргументов, оно просто будет дословно передано программе, независимо от того, существует файл с таким именем или нет. Perl действует точно так же, поэтому, пока мы гарантируем, что нет никаких символов, которые являются специальными для оболочки или Perl между <
и соответствием >
, мы можем эффективно использовать это как действительно неуклюжую форму строкового литерала. Более того, синтаксический анализатор Perl для операторов, подобных кавычкам, имеет навязчивую тенденцию сопоставлять скобки даже в таких контекстах, как этот, где это не имеет смысла, поэтому мы можем <>
безопасно вкладывать (что необходимо для обнаружения этой программы). Основным недостатком всех этих вложенных <>
является то, что экранирование содержимого<>
почти невозможно; Кажется, что у каждого есть два слоя эскалации <>
, поэтому, чтобы избежать чего-то на внутренней стороне всех трех, ему должно предшествовать 63 обратных слэша. Я решил, что хотя размер кода является лишь второстепенным соображением в этой проблеме, почти наверняка не стоит платить такого рода штраф за мой счет, поэтому я просто решил написать остальную часть программы без использования оскорбительных символов.
Так что же произойдет, если части оболочки будут удалены?
- Исключения в слове
eval
превращают его в голое слово , строку без смысла. Perl не любит их, но обращается с ними так, как будто они заключены в кавычки; таким образом eal<+eval<+...
интерпретируется как"eal" < +eval<+...
, Это никак не влияет на работу программы, потому что она в основном просто берет результат из сильно вложенных evals (которые мы не используем в любом случае), приводим его к целому числу и выполняем некоторые бессмысленные сравнения с ним. (Подобные вещи вызывают много спам-предупреждений, поскольку в обычных условиях это явно бесполезно; мы просто используем их для поглощения удалений.) Это изменяет количество необходимых угловых скобок (потому что открывающая скобка теперь вместо этого интерпретируется как оператор сравнения), но цепочка комментариев в конце обеспечивает безопасное завершение строки независимо от того, сколько раз она была вложена. (Здесь больше #
знаков, чем строго необходимо; я написал так, как сделал, чтобы сделать программу более сжимаемой, позволяя мне использовать меньше данных для хранения квин.)
- Если a
<
удаляется, код теперь анализируется как eval(eval<...>)
. Вторичный, external не eval
имеет никакого эффекта, потому что программы, которые мы оцениваем, не возвращают ничего, что имеет какой-либо реальный эффект, как программа (если они вообще возвращаются нормально, обычно это нулевая строка или голое слово; чаще они возвращаются через исключение, которое вызывает eval
возврат пустой строки или использование, exit
чтобы вообще не возвращать).
- Если a
+
удаляется, это не имеет немедленного эффекта, если смежный код не поврежден; Унарный +
не влияет на программу. (Причина, по которой исходные +
s существуют, заключается в том, чтобы помочь исправить повреждения; они увеличивают число ситуаций, в которых <
интерпретируется как унарный, <>
а не как реляционный оператор, что означает, что вам нужно больше удалений для создания недопустимой программы.)
Оболочка может быть повреждена с достаточным количеством удалений, но вам нужно выполнить серию удалений, чтобы создать что-то, что не анализируется. С помощью четырех удалений вы можете сделать это:
eal<evl<eval+<...
а в Perl реляционный оператор <
неассоциативен, и, таким образом, вы получаете синтаксическую ошибку (ту же, что и с 1<2<3
). Таким образом, ограничение для написанной программы составляет n = 3. Добавление более унарного кода +
кажется многообещающим способом его увеличения, но, поскольку это повысит вероятность того, что внутренняя часть оболочки также может сломаться, проверить, что новая версия программы работает, может быть очень сложно.
Причина, по которой оболочка так ценна, заключается в том, что eval
в Perl ловит исключения, такие как (например) исключение, которое вы получаете, когда пытаетесь скомпилировать синтаксическую ошибку. Поскольку это eval
строковый литерал, компиляция строки происходит во время выполнения, и если литерал не компилируется, полученное исключение будет поймано. Это заставляет eval
возвращать пустую строку и устанавливать индикатор ошибки $@
, но мы никогда не проверяем это (кроме случаев, когда время от времени выполняем возвращенную пустую строку в нескольких видоизмененных версиях программы). Важно то, что если что-то должно случиться с кодом внутриОболочка, вызывающая синтаксическую ошибку, затем оболочка просто заставит код ничего не делать (и программа продолжит выполнение в попытке найти неповрежденную копию себя). Следовательно, внутренний код не должен быть почти таким же защищенным от радиации, как оболочка; все, что нас волнует, - это то, что если он будет поврежден, он будет либо действовать идентично неповрежденной версии программы, либо завершится сбоем (позволяя eval
перехватить исключение и продолжить), либо завершится нормально, ничего не печатая.
Внутри обертки
Код внутри оболочки, по сути, выглядит следующим образом (опять же, есть элемент управления, _
который Stack Exchange не покажет сразу перед -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Этот код написан полностью с использованием глобальных символов, и его целью является добавление нового алфавита знаков препинания, который позволяет писать настоящую программу, путем транслитерации и оценки строкового литерала (мы не можем использовать '
или в "
качестве нашей цитаты отмечает, но q(
... )
также является допустимым способом формирования строки в Perl). (Причина непечатаемого символа в том, что нам нужно транслитерировать что-то в символ пробела без буквального пробела в программе; таким образом, мы формируем диапазон, начинающийся с ASCII 31, и ловим пробел как второй элемент диапазона.) Очевидно, что если мы производим некоторые символы посредством транслитерации, мы должны пожертвовать символами, чтобы транслитерировать их из, но заглавные буквы не очень полезны, и гораздо проще писать без доступа к ним, чем без доступа к знакам препинания.
Вот алфавит знаков препинания, которые становятся доступными в результате глобуса (верхняя строка показывает кодировку, нижняя строка - символ, который она кодирует):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +;? <=> @ Агги {|} ~
В частности, у нас есть куча знаков препинания, которые не являются глобальными, но полезны при написании Perl-программ вместе с символом пробела. Я также сохранил две заглавные буквы, литерал A
и Z
(которые кодируют не сами по себе, а в T
и U
, потому что это A
было необходимо в качестве конечной точки верхнего и нижнего диапазона); это позволяет нам написать саму инструкцию транслитерации, используя новый набор кодированных символов (хотя заглавные буквы не так полезны, они полезны при определении изменений заглавных букв). Наиболее заметными символами, которых у нас нет, являются [
, \
и ]
, но ни один из них не нужен (когда мне понадобился символ новой строки в выводе, я создал его, используя неявный символ новой строки изsay
вместо того, чтобы писать \n
; chr 10
также сработало бы, но более многословно).
Как обычно, нам нужно беспокоиться о том, что произойдет, если внутренняя часть оболочки будет повреждена вне строкового литерала. Поврежденный eval
предотвратит что-либо работающее; мы в порядке с этим. Если кавычки повреждены, внутренняя часть строки не является допустимым Perl, и, таким образом, оболочка поймает его (а многочисленные вычитания в строках означают, что даже если вы могли бы сделать это допустимым Perl, он ничего не будет делать, что это приемлемый результат). Повреждение в транслитерацию, если это не ошибка синтаксиса, будет искажать строку оценивается, как правило , в результате чего он стал синтаксической ошибкой; Я не уверен на 100%, что нет ни одного случая, когда это сломалось бы, но я в настоящий момент пытаюсь это сделать грубо, и это должно быть достаточно легко исправить, если таковые имеются.
Закодированная программа
Посмотрев внутрь строкового литерала, изменив кодировку, которую я использовал, и добавив пробел, чтобы сделать его более читабельным, мы получим это (опять же, представьте подчеркивание элемента управления перед символом -+
, который закодирован как A
):
$o=q<
length$o ==181 || zzzz((()));
do {
chop ($t = "eval+<"x4);
$r = '=-+;-AZz-~=';
$s = '$o=q<' . $o . '>;eval$o';
eval '$s=~y' . $r . 'A-Z=';
say "$t(q($s)=~y=A-Z${r}r)" . "####>"x6;
say ";" for 1..4
} for 1..4;
exit>;
eval $o
Люди, привыкшие к куинам, узнают эту общую структуру. Самая важная часть находится в начале, где мы проверяем, что $ o не поврежден; если символы были удалены, его длина не будет соответствовать 181
, поэтому мы бежим , zzzz((()))
которые, если это не ошибка синтаксиса из - за несогласованные скобки, будет ошибкой времени выполнения , даже если вы удалите все три символа, потому что ни один из zzzz
, zzz
, zz
, и z
является функцией, и нет способа предотвратить ее синтаксический анализ как функцию, кроме удаления (((
и появления явной синтаксической ошибки. Сам чек также невосприимчив к повреждениям; ||
может быть поврежден в |
но заставит zzzz((()))
вызов бежать безоговорочно; повреждая переменные или константы приведет к несоответствию , потому что вы сравниваете один из 0
,180
, 179
, 178
Для равенства некоторого подмножества цифр 181
; и удаление одного =
вызовет сбой разбора, а два =
неизбежно приведут к тому, что LHS вычислит либо целое число 0, либо пустую строку, обе из которых являются ложными.
Обновление : эта проверка была немного ошибочной в предыдущей версии программы, поэтому мне пришлось отредактировать ее, чтобы исправить проблему. Предыдущая версия выглядела так после декодирования:
length$o==179||zzzz((()))
и было возможно удалить первые три знака препинания, чтобы получить это:
lengtho179||zzz((()))
lengtho179
, будучи голым словом, правдива и, таким образом, нарушает чек. Я исправил это, добавив два дополнительных B
символа (которые кодируют пробелы), что означает, что последняя версия quine делает это:
length$o ==181||zzzz((()))
Теперь невозможно скрыть как =
знаки, так и $
знак без синтаксической ошибки. (Мне пришлось добавить два пробела, а не один, потому что из-за длины в источник помещался 180
бы буквальный 0
символ, который можно было бы использовать в этом контексте для сравнения целых чисел с нулевым словом, что успешно.) Конец обновления
После того, как проверка длины прошла, мы знаем, что копия не повреждена, по крайней мере, с точки зрения удаления символов из нее, так что все это просто прямое цитирование оттуда (замены знаков препинания из-за поврежденной таблицы декодирования не будут пойманы с этой проверкой , но я уже подтвердил с помощью грубой форсировки, что никакие три удаления только из таблицы декодирования не нарушают квинну (предположительно, большинство из них вызывают синтаксические ошибки). У нас уже $o
есть переменная, поэтому все, что нам нужно сделать, - это жестко закодировать внешние оболочки (с небольшой степенью сжатия; я не пропустил полностью часть вопроса о коде-гольфе ). Одна хитрость в том, что мы храним основную часть таблицы кодирования в$r
; мы можем либо распечатать его буквально, чтобы сгенерировать секцию таблицы кодирования внутренней оболочки, либо объединить некоторый код вокруг eval
него, чтобы запустить процесс декодирования в обратном порядке (что позволяет нам выяснить, что такое кодированная версия $ o , имея только декодированную версию, доступную на данный момент).
Наконец, если мы были неповрежденной копией и, таким образом, могли вывести всю оригинальную программу, мы вызываем ее exit
, чтобы другие копии также не пытались распечатать программу.
Скрипт проверки
Не очень красиво, но размещать его, потому что кто-то спросил. Я запускал это несколько раз с различными настройками (обычно меняющимися $min
и $max
проверяющими различные области интереса); это был не полностью автоматизированный процесс. Он имеет тенденцию останавливаться из-за большой загрузки процессора в других местах; когда это произошло, я просто переключился $min
на первое значение, $x
которое не было полностью проверено, и продолжил выполнение сценария (таким образом, гарантируя, что все программы в диапазоне были проверены в конечном итоге). Я проверил удаление только из первой копии программы, потому что совершенно очевидно, что удаление из других копий не может сделать больше.
use 5.010;
use IPC::Run qw/run/;
undef $/;
my $program = <>;
my $min = 1;
my $max = (length $program) / 4 - 3;
for my $x ($min .. $max) {
for my $y ($x .. $max) {
for my $z ($y .. $max) {
print "$x, $y, $z\n";
my $p = $program;
substr $p, $x, 1, "";
substr $p, $y, 1, "";
substr $p, $z, 1, "";
alarm 4;
run [$^X, '-M5.010'], '<', \$p, '>', \my $out, '2>', \my $err;
if ($out ne $program) {
print "Failed deleting at $x, $y, $z\n";
print "Output: {{{\n$out}}}\n";
exit;
}
}
}
}
say "All OK!";
Subleq
. Я думаю, что это было бы идеально для такого рода задач!