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)####>####>####>####>####>####>
;
;
;
;
Пояснення
Ця програма, цілком чітко, складається з чотирьох однакових менших програм, об'єднаних разом. Основна ідея полягає в тому, що кожна копія програми буде перевіряти, чи була вона пошкоджена занадто сильно для запуску чи ні; якщо це було, він нічого не зробить (крім можливого загострення попереджень) і пустить наступну копію; якщо цього не було (тобто немає видалень, або вилучений символ був таким, який не має ніякої різниці в роботі програми), він зробить свою надзвичайно важливу річ (роздрукувавши вихідний код повного програмного забезпечення; це належна квітка, з кожною частиною, що містить кодування всього вихідного коду), а потім вийти (запобігти повторному друку вихідного коду будь-якими непошкодженими копіями та тим самим зруйнувати ланцюжок, надрукувавши занадто багато тексту).
Кожна частина у свою чергу складається з двох частин, які ефективно функціонально незалежні; зовнішня обгортка та деякий внутрішній код. Як такі ми можемо розглядати їх окремо.
Зовнішня обгортка
Зовнішня обгортка - це, як правило, eval<+eval<+eval< ... >####>####...>###
(плюс купу крапок з крапками та новими рядками, призначення яких повинно бути досить очевидним; це забезпечити, щоб частини програми залишилися відокремленими незалежно від того, чи буде видалено деякі крапки з комою або нові рядки перед ними ). Це може виглядати досить просто, але це дуже тонко в багатьох напрямках, і тому я вибрав Perl для цього виклику.
Спочатку розглянемо, як функціонує обгортка у непошкодженій копії програми. eval
аналізує як вбудовану функцію, яка бере один аргумент. Оскільки очікується аргументація, +
тут є одинарний +
(який вже буде добре знайомий гольфістам Perl; вони корисні на диво часто). Ми все ще очікуємо на аргумент (ми щойно побачили одинарного оператора), тому <
наступне трактується як початок <>
оператора (який не приймає аргументів префікса чи постфікса, і, таким чином, може використовуватися в операндному положенні).
<>
- досить дивний оператор. Його звичайне призначення - читати файлові файли, і ви розміщуєте ім'я файлових файлів всередині кутових дужок. Крім того, якщо вираз недійсний як ім'я файлового файлу, він робить глобальний (в основному, той самий процес, який оболонки UNIX використовують для перекладу тексту, введеного користувачем, в послідовність аргументів командного рядка; набагато старіші версії Perl фактично використовуються оболонка для цього, але в наш час Perl обробляє глобул всередині). Таким чином, передбачуване використання є таким чином, <*.c>
як правило, повертає такий список ("foo.c", "bar.c")
. У скалярному контексті (наприклад, аргумент доeval
), він просто повертає перший запис, який він виявляє при першому його запуску (еквівалент першого аргументу), і повертає інші записи в гіпотетичних майбутніх запусках, які ніколи не відбудуться.
Тепер оболонки часто обробляють аргументи командного рядка; якщо ви надаєте щось на зразок -r
без аргументів, воно просто передається програмі дослівно, незалежно від того, є файл з таким ім'ям чи ні. Perl діє так само, до тих пір, поки ми гарантуємо, що між символами <
та співпаданнями немає символів, які є особливими для оболонки або Perl >
, ми можемо ефективно використовувати це, як справді незграбна форма літерального рядка. Ще краще, що парсер для синтаксичних операторів Perl має нав'язливу тенденцію до узгодження дужок навіть у таких контекстах, як це, де це не має сенсу, тому ми можемо <>
безпечно гніздоватись (для цього можливо відкриття, необхідне для цієї програми). Основним недоліком усіх цих вкладених <>
є те, що уникнути вмісту<>
майже неможливо; здається, що два шари не розгортаються з кожним <>
, тому, щоб уникнути чогось із внутрішньої сторони всіх трьох, йому потрібно передувати 63 нахилів. Я вирішив, що незважаючи на те, що розмір коду є лише вторинною увагою у цій проблемі, майже напевно не варто було платити такий штраф за мій рахунок, тому я просто вирішив написати решту програми, не використовуючи символів, що ображають.
Отже, що відбувається, якщо частини обгортки видалити?
- Вилучення у слові
eval
призводить до того, що воно перетворюється на голове слово - рядок без значення. Перлу це не подобається, але він трактує їх так, ніби їх оточили цитатами; таким чином eal<+eval<+...
трактується як"eal" < +eval<+...
. Це не впливає на роботу програми, оскільки в основному це просто отримання результату з сильно вкладених овалів (якими ми все одно не користуємося), закидання його на ціле число та проведення безглуздих порівнянь. (Така річ викликає багато спам-попередження, оскільки це в звичайних обставинах явно не корисно; ми просто використовуємо її для поглинання видалень.) Це змінює кількість потрібних нам дужок кутів закриття (тому що відкриваюча дужка зараз інтерпретується замість цього оператора порівняння), але ланцюжок коментарів наприкінці гарантує, що рядок закінчиться благополучно незалежно від того, скільки разів він вкладений. (Тут є більше #
знаків, ніж суворо потрібно; я написав це так, як це робив для того, щоб зробити програму більш стисливою, дозволяючи мені використовувати менше даних для зберігання лайки.)
- Якщо
<
видалення видаляється, код тепер розбирається як eval(eval<...>)
. Вторинне, зовні, eval
не має ефекту, тому що програми, які ми оцінюємо, не повертають нічого, що має реальну дію, як програма (якщо вони взагалі повертаються нормально, це звичайно нульовий рядок або барево; частіше вони повертаються через виняток, який змушує eval
повернути нульову рядок або використовувати exit
для уникнення повернення взагалі).
- Якщо
+
видалення видаляється, це не має негайного ефекту, якщо сусідній код недоторканий; Унарі +
не впливає на програму. (Причина оригіналу +
полягає в тому, щоб допомогти відновити пошкодження; вони збільшують кількість ситуацій, в яких <
інтерпретується як унарний, <>
а не як реляційний оператор. Це означає, що для отримання недійсної програми потрібно більше видалення.)
Обгортку можна пошкодити достатньою кількістю видалень, але вам потрібно зробити ряд видалень, щоб створити щось, що не розбирає. За допомогою чотирьох видалень ви можете це зробити:
eal<evl<eval+<...
і в Perl реляційний оператор <
неасоціативний, і, таким чином, ви отримуєте синтаксичну помилку (таку саму, яку ви отримали б 1<2<3
). Таким чином, обмеження для написаної програми дорівнює n = 3. Додавання більшої кількості одинарних +
видається перспективним способом її збільшення, однак це зробить все більш імовірним, що всередині оболонки може також зламатися, переконавшись, що нова версія програми може бути дуже складною.
Причина, яку обгортка настільки цінна, полягає eval
в тому, що в Perl виводяться винятки, такі як (наприклад) виняток, який ви отримуєте при спробі скласти помилку синтаксису. Оскільки це eval
строковий літерал, компіляція рядка відбувається під час виконання, і якщо літерал не вдається компілювати, отриманий виняток потрапляє. Це призводить eval
до повернення нульового рядка та встановлення індикатора помилок $@
, але ми його ніколи не перевіряємо (за винятком випадкового виконання повернутої нульової рядки в кількох мутованих версіях програми). Кардинально, це означає , що якщо що - щось має статися з кодом всерединіобгортка, викликаючи синтаксичну помилку, тоді обгортка просто змусить код не робити нічого (і програма буде виконувати виконання, намагаючись знайти непошкоджену копію себе). Тому внутрішній код не повинен бути настільки захищеним від радіації, як обгортка; все, що нас хвилює, це те, що якщо він пошкоджений, він буде діяти однаково до непошкодженої версії програми, або в іншому випадку вийде з ладу (дозволяючи eval
зловити виняток і продовжити) або вийти нормально, не надрукуючи нічого.
Всередині обгортки
Код всередині обгортки, по суті, виглядає приблизно так (знову ж таки, існує Control - _
що обмін стеками не з’явиться безпосередньо перед цим -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Цей код написаний повністю з захищеними глобальними символами, і його мета - додати новий алфавіт розділових знаків, що дасть змогу написати реальну програму, транслітерацією та оцінкою рядкового букваря (ми не можемо використовувати '
або "
як нашу цитату знаків, але q(
... )
також є дійсним способом формування рядка в Perl). (Причина недрукуваного символу полягає в тому, що нам потрібно щось транслітерувати в пробільний символ без буквального символу пробілу в програмі; таким чином ми формуємо діапазон, починаючи з ASCII 31, і вловлюємо простір як другий елемент діапазону.) очевидно, що якщо ми виробляти деякі символи через транслітерацію, ми повинні пожертвувати символи транслітерувати їх з, але великі літери не дуже корисні, і писати набагато простіше без доступу до тих, ніж без доступу до розділових знаків.
Ось алфавіт розділових знаків, які стають доступними в результаті глобуса (верхній рядок показує кодування, нижній рядок - символ, який він кодує):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Найбільше, що у нас є купа розділових знаків, які не є глобальними, але корисні при написанні програм 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
. Я думаю, що це було б ідеально для такого виклику!