Perl, 1116 1124 bajtów, n = 3, wynik = 1124 ^ (2/3) lub około 108,1
Aktualizacja : Sprawdziłem teraz, że działa to przy n = 3 za pomocą brutalnej siły (co zajęło kilka dni); z programem tego kompleksu trudno jest ręcznie sprawdzić odporność na promieniowanie (i popełniłem jeden błąd w poprzedniej wersji, dlatego wzrosła liczba bajtów). Zakończ aktualizację
Polecam przekierowanie stderr gdzieś, gdzie go nie zobaczysz; ten program generuje mnóstwo ostrzeżeń o podejrzanej składni, nawet jeśli nie usuwasz z niej znaków.
Możliwe jest, że program może zostać skrócony. Praca nad tym jest dość bolesna, dzięki czemu łatwo przeoczyć możliwe mikrooptymalizacje. Miałem głównie na celu jak największą liczbę usuwalnych znaków (ponieważ jest to naprawdę trudna część programu) i potraktowałem grę w golfa jako coś, co było miłym celem, ale jako coś, czego nie chciałbym umieścić niedorzeczny wysiłek na rzecz optymalizacji (na podstawie tego, że bardzo łatwo jest przypadkowo przełamać odporność na promieniowanie).
Program
Uwaga: _
tuż przed każdym z czterech wystąpień występuje dosłowny znak kontrolny (ASCII 31) -+
. Nie sądzę, aby poprawnie skopiował i wkleił do StackOverflow, więc będziesz musiał dodać go ponownie przed uruchomieniem programu.
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)####>####>####>####>####>####>
;
;
;
;
Wyjaśnienie
Program ten jest dość wyraźnie złożony z czterech identycznych mniejszych programów połączonych razem. Podstawową ideą jest to, że każda kopia programu sprawdzi, czy została zbyt poważnie uszkodzona, aby ją uruchomić, czy nie; jeśli tak, nie zrobi nic (oprócz ostrzeżeń wypluwających) i uruchomi następną kopię; jeśli tak nie było (tzn. nie usunięto, lub usunięty znak był tym, który nie ma znaczenia dla działania programu), zrobi to, co w jego mocy (wypisanie kodu źródłowego pełnego programu; to jest właściwa quine, z każdej strony zawierającej kodowania całego kodu źródłowego), następnie wyjście (zapobieganie inne nieuszkodzone kopii drukowanie kodu źródłowego się ponownie i w ten sposób niszcząc Quine'a drukując zbyt tekst).
Każda część z kolei składa się z dwóch części, które są funkcjonalnie niezależne; opakowanie zewnętrzne i trochę kodu wewnętrznego. Jako takie możemy rozpatrywać je osobno.
Zewnętrzne opakowanie
Zewnętrzne opakowanie jest w zasadzie eval<+eval<+eval< ... >####>####...>###
(plus wiązka średników i znaków nowej linii, których cel powinien być dość oczywisty; ma to zapewnić, że części programu pozostaną oddzielone niezależnie od tego, czy niektóre średniki lub nowe znaki przed nimi zostaną usunięte ). To może wydawać się dość proste, ale jest subtelne na wiele sposobów i dlatego wybrałem Perla do tego wyzwania.
Najpierw przyjrzyjmy się, jak działa opakowanie w nieuszkodzonej kopii programu. eval
parsuje jako funkcja wbudowana, która przyjmuje jeden argument. Ponieważ spodziewany jest spór, +
tutaj jest jedno +
(co będzie już bardzo znane golfistom Perla; przydają się zaskakująco często). Nadal oczekujemy argumentu (właśnie widzieliśmy jednoargumentowy operator), więc <
następny jest interpretowany jako początek <>
operatora (który nie przyjmuje argumentów przedrostkowych lub postfiksowych, a zatem może być użyty w pozycji argumentu).
<>
jest dość dziwnym operatorem. Jego zwykłym celem jest odczytywanie uchwytów plików i umieszczanie nazwy uchwytu pliku w nawiasach kątowych. Alternatywnie, jeśli wyrażenie nie jest poprawne jako nazwa uchwytu pliku, dokonuje globowania (w zasadzie ten sam proces, którego używają powłoki UNIX do tłumaczenia tekstu wprowadzonego przez użytkownika na sekwencję argumentów wiersza poleceń; faktycznie używane były znacznie starsze wersje Perla powłoka do tego, ale obecnie Perl zajmuje się globowaniem wewnętrznie). Dlatego zamierzone użycie jest zgodne z <*.c>
, co zwykle zwraca listę podobną do ("foo.c", "bar.c")
. W kontekście skalarnym (np. Argument doeval
), po prostu zwraca pierwszy wpis, który znajdzie przy pierwszym uruchomieniu (odpowiednik pierwszego argumentu), i zwraca inne wpisy dotyczące hipotetycznych przyszłych przebiegów, które nigdy się nie zdarzają.
Teraz powłoki często obsługują argumenty wiersza poleceń; jeśli podasz coś -r
bez argumentów, zostanie ono przekazane dosłownie do programu, niezależnie od tego, czy istnieje plik o takiej nazwie, czy nie. Perl działa w ten sam sposób, tak długo, jak upewniamy się, że nie ma żadnych znaków, które byłyby specjalne dla powłoki lub Perla między <
dopasowaniem a dopasowaniem >
, możemy efektywnie używać tego jak naprawdę niezręcznej formy literału łańcuchowego. Co więcej, parser Perla dla operatorów podobnych do cytatów ma kompulsywną tendencję do dopasowywania nawiasów, nawet w kontekstach takich jak ten, w którym nie ma to sensu, więc możemy <>
bezpiecznie zagnieżdżać się (co jest odkryciem potrzebnym dla tego programu). Głównym minusem wszystkich tych zagnieżdżonych <>
jest to, że uciekają one od zawartości<>
jest prawie niemożliwe; Wydaje się, że istnieją dwie warstwy odskakiwania z każdą <>
, więc aby uciec przed czymś we wszystkich trzech, należy poprzedzić je 63 odwrotnymi ukośnikami. Uznałem, że mimo iż rozmiar kodu jest jedynie drugorzędną kwestią w tym problemie, prawie na pewno nie warto było płacić tego rodzaju kar za mój wynik, więc postanowiłem napisać resztę programu bez użycia obrażających znaków.
Co się stanie, jeśli części opakowania zostaną usunięte?
- Usunięcie słowa
eval
powoduje, że zamienia się ono w jedno słowo , ciąg bez znaczenia. Perl ich nie lubi, ale traktuje je tak, jakby były otoczone cytatami; dlatego eal<+eval<+...
jest interpretowane jako"eal" < +eval<+...
. Nie ma to wpływu na działanie programu, ponieważ po prostu bierze wynik z mocno zagnieżdżonych evals (których i tak nie używamy), rzucając go na liczbę całkowitą i dokonując na nim bezsensownych porównań. (Ten rodzaj rzeczy powoduje wiele spamujących ostrzeżeń, ponieważ najwyraźniej nie jest to przydatne w normalnych okolicznościach; używamy go tylko do pochłaniania usunięć). Zmienia to liczbę potrzebnych nawiasów zamykających (ponieważ nawias otwierający jest potrzebny jest teraz interpretowany jako operator porównania), ale łańcuch komentarzy na końcu zapewnia, że łańcuch zakończy się bezpiecznie bez względu na to, ile razy jest zagnieżdżony. (Jest tu więcej #
znaków niż jest to absolutnie potrzebne; napisałem to tak, jak zrobiłem, aby uczynić program bardziej kompresowalnym, pozwalając mi używać mniej danych do przechowywania quine.)
- Jeśli a
<
zostanie usunięte, kod będzie teraz analizowany jako eval(eval<...>)
. Drugorzędne, zewnętrzne eval
nie ma żadnego efektu, ponieważ oceniane przez nas programy nie zwracają niczego, co ma jakikolwiek rzeczywisty efekt jako program (jeśli w ogóle zwracają normalnie, zwykle jest to ciąg zerowy lub jedno słowo; częściej wracają przez wyjątek, który powoduje eval
zwrócenie ciągu zerowego lub użycie, exit
aby w ogóle nie zwracać).
- Usunięcie a
+
nie ma natychmiastowego efektu, jeśli sąsiedni kod jest nienaruszony; unary +
nie ma wpływu na program. (Powodem, dla którego +
są tam oryginalne, jest pomoc w naprawianiu szkód; zwiększają one liczbę sytuacji, w których <
interpretowane są jako jednoargumentowe, <>
a nie jako operator relacyjny, co oznacza, że potrzebujesz więcej usunięć, aby utworzyć nieprawidłowy program).
Opakowanie może zostać uszkodzone przez wystarczającą liczbę usunięć, ale musisz wykonać serię usunięć, aby uzyskać coś, co nie zostanie przeanalizowane. Dzięki czterem usunięciom możesz to zrobić:
eal<evl<eval+<...
a w Perlu operator relacyjny nie <
jest asocjatywny, dlatego otrzymujesz błąd składniowy (ten sam, który dostaniesz 1<2<3
). W związku z tym ograniczenie dla programu w formie, w jakiej napisano, wynosi n = 3. Dodanie większej liczby jednostek jednoosobowych +
wydaje się obiecującym sposobem na jego zwiększenie, ale ponieważ zwiększałoby to prawdopodobieństwo, że wnętrze opakowania również mogłoby się zepsuć, sprawdzając, czy nowa wersja programu może być bardzo trudna.
Powodem, dla którego opakowanie jest tak cenne, jest to, że eval
w Perlu wyłapuje się wyjątki, takie jak (na przykład) wyjątek, który występuje podczas próby skompilowania błędu składniowego. Ponieważ eval
jest to literał łańcuchowy, kompilacja łańcucha zachodzi w czasie wykonywania, a jeśli literał się nie skompiluje, wyjątek zostanie przechwycony. Powoduje eval
to zwrócenie łańcucha zerowego i ustawienie wskaźnika błędu $@
, ale my też nigdy nie sprawdzamy (z wyjątkiem sporadycznego wykonywania zwróconego łańcucha zerowego w kilku zmutowanych wersjach programu). Co najważniejsze, oznacza to, że jeśli coś się stanie z kodem w środkuotoki, powodując błąd składniowy, otoki po prostu powodują, że kod nic nie robi (zamiast tego program będzie kontynuował próbę znalezienia nieuszkodzonej kopii siebie). Dlatego wewnętrzny kod nie musi być prawie tak odporny na promieniowanie jak opakowanie; wszystko, na czym nam zależy, to to, że jeśli zostanie uszkodzony, będzie działał identycznie jak w przypadku nieuszkodzonej wersji programu, albo zawiesi się (pozwalając eval
złapać wyjątek i kontynuować) lub zakończy normalnie bez drukowania niczego.
Wewnątrz opakowania
Zasadniczo kod wewnątrz opakowania wygląda następująco (ponownie, istnieje Kontrola - _
stos wymiany nie pokaże się bezpośrednio przed -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Ten kod jest napisany w całości ze znakami bezpiecznymi dla całego świata, a jego celem jest dodanie nowego alfabetu znaków interpunkcyjnych, które umożliwiają napisanie prawdziwego programu, poprzez transliterację i ocenę literału łańcuchowego (nie możemy używać '
ani "
jako naszego cytatu znaczniki, ale q(
… )
jest także prawidłowym sposobem tworzenia łańcucha w Perlu). (Powodem, dla którego nie można drukować, jest to, że musimy transliterować coś do znaku spacji bez dosłownego znaku spacji w programie; w ten sposób tworzymy zakres zaczynający się od ASCII 31 i przechwytujemy spację jako drugi element zakresu.) oczywiście, jeśli mamy do produkcji niektórych znaków poprzez transliteracji, musimy poświęcić znaki transliterować je od, ale wielkie litery nie są zbyt przydatne i o wiele łatwiej jest pisać bez dostępu do nich niż bez dostępu do znaków interpunkcyjnych.
Oto alfabet znaków interpunkcyjnych, które stają się dostępne w wyniku glob (górna linia pokazuje kodowanie, dolna linia koduje znak):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Przede wszystkim mamy kilka znaków interpunkcyjnych, które nie są globalnie bezpieczne, ale są przydatne do pisania programów w języku Perl, wraz ze znakiem spacji. Zapisałem również dwie wielkie litery, literalne A
i Z
(które kodują nie same w sobie, ale do T
i U
, ponieważ A
były potrzebne jako punkt końcowy górnego i dolnego zakresu); pozwala nam to napisać instrukcję transliteracji z użyciem nowego zestawu zakodowanych znaków (chociaż wielkie litery nie są tak przydatne, są przydatne do określania zmian wielkich liter). Najbardziej zauważalną znaków, że nie mamy dostępne są [
, \
i ]
, ale nie są potrzebne (gdy potrzebowałem nowego wiersza na wyjściu, wyprodukowałem go za pomocą nowej linii z niejawnegosay
zamiast pisać \n
; chr 10
również by działało, ale jest bardziej szczegółowe).
Jak zwykle musimy martwić się, co się stanie, jeśli wnętrze opakowania zostanie uszkodzone poza dosłownym ciągiem znaków. Zepsuty eval
uniemożliwi działanie czegokolwiek; nic nam nie jest. Jeśli znaki cudzysłowu ulegną uszkodzeniu, wnętrze łańcucha nie jest prawidłowe w Perlu, a zatem opakowanie go złapie (a liczne odejmowanie ciągów oznacza, że nawet gdybyś mógł uczynić go poprawnym w Perlu, nie zrobiłby nic, co jest akceptowalnym wynikiem). Uszkodzenie transliteracji, jeśli nie jest to błąd składniowy, zakłóci oceniany ciąg znaków, zwykle powodując, że stanie się on błędem składni; Nie jestem w 100% pewien, że nie ma przypadków, w których to się psuje, ale w tej chwili zmuszam się do brutalnego zmuszania, aby się upewnić, i jeśli tak, to powinno być łatwe do naprawienia.
Zakodowany program
Zaglądając do literału ciągu, odwracając używane kodowanie i dodając białe znaki, aby było bardziej czytelne, otrzymujemy to (ponownie, wyobraź sobie kontrolny znak podkreślenia przed -+
, który jest kodowany jako 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
Ludzie przyzwyczajeni do quinesów rozpoznają tę ogólną strukturę. Najważniejsza część znajduje się na początku, gdzie weryfikujemy, czy $ nie jest uszkodzony; Jeżeli znaki zostały usunięte, jego długość nie będzie pasował 181
, więc prowadzimy zzzz((()))
które, jeśli to nie jest błąd składni powodu nawiasach niedopasowanych, będzie runtime error, nawet jeśli usunąć wszystkie trzy znaki, ponieważ żaden z zzzz
, zzz
, zz
, i z
jest funkcją, i nie ma innego sposobu, aby zapobiec analizowaniu go jako funkcji innej niż usunięcie (((
i spowodowanie błędnego składni. Sam czek jest również odporny na uszkodzenia; ||
mogą być uszkodzone |
, ale to spowoduje zzzz((()))
wywołanie uruchomić bezwarunkowo; uszkodzenie zmiennych lub stałych spowoduje niedopasowanie, ponieważ porównujesz jedną z nich 0
,180
, 179
, 178
Równości do pewnego podzbioru cyfr 181
; a usunięcie jednego =
spowoduje błąd analizy, a dwa =
nieuchronnie spowodują, że LHS będzie oceniać na liczbę całkowitą 0 lub ciąg zerowy, z których oba są falsey.
Aktualizacja : Ta kontrola była nieco niepoprawna w poprzedniej wersji programu, więc musiałem ją edytować, aby rozwiązać problem. Poprzednia wersja wyglądała tak po dekodowaniu:
length$o==179||zzzz((()))
możliwe było usunięcie pierwszych trzech znaków interpunkcyjnych:
lengtho179||zzz((()))
lengtho179
, będąc gołym słowem, jest prawdomówne i tym samym łamie czek. Naprawiłem to, dodając dodatkowe dwa B
znaki (które kodują spacje), co oznacza, że najnowsza wersja quine robi to:
length$o ==181||zzzz((()))
Teraz nie można ukryć zarówno =
znaków, jak i $
znaku bez spowodowania błędu składniowego. (Musiałem dodać dwie spacje zamiast jednej, ponieważ długość 180
wstawiłaby dosłowny 0
znak do źródła, który może być wykorzystany w tym kontekście do porównania liczb całkowitych z jednym gołym słowem, co się powiedzie.) Koniec aktualizacji
Po sprawdzeniu długości wiemy, że kopia jest nieuszkodzona, przynajmniej pod względem usuwania znaków z niej, więc wszystko jest po prostu proste stamtąd (zastąpienie znaków interpunkcyjnych z powodu uszkodzonej tabeli dekodowania nie zostałoby złapane przy tym sprawdzeniu) , ale już sprawdziłem za pomocą brutalnego wymuszania, że żadne trzy usunięcia tylko z tabeli dekodowania nie powodują przerwania quine; przypuszczalnie większość z nich powoduje błędy składniowe). Mamy $o
już zmienną, więc wszystko, co musimy zrobić, to na stałe zakleić zewnętrzne opakowania (z pewnym niewielkim stopniem kompresji; nie pominąłem całkowicie pytania dotyczącego golfa ). Jedną sztuczką jest to, że przechowujemy większość tabeli kodowania$r
; możemy albo wydrukować go dosłownie, aby wygenerować sekcję tabeli kodowania wewnętrznego opakowania, lub połączyć w sobie część kodu i eval
to w celu uruchomienia procesu dekodowania w odwrotnej kolejności (co pozwala nam dowiedzieć się, co to jest zakodowana wersja $ o , mając w tym momencie tylko zdekodowaną wersję).
Wreszcie, jeśli byliśmy nienaruszoną kopią, a tym samym mogliśmy exit
wypisać cały oryginalny program, wywołujemy , aby inne kopie również nie próbowały wydrukować programu.
Skrypt weryfikacyjny
Niezbyt ładnie, ale publikowanie, ponieważ ktoś zapytał. Uruchomiłem to kilka razy z różnymi ustawieniami (zwykle zmieniając $min
i $max
sprawdzając różne obszary zainteresowań); nie był to w pełni zautomatyzowany proces. Ma tendencję do zatrzymywania się z powodu dużego obciążenia procesora w innym miejscu; kiedy to się stało, właśnie zmieniłem $min
na pierwszą wartość, $x
która nie była w pełni sprawdzona i kontynuowałem uruchamianie skryptu (tym samym upewniając się, że ostatecznie wszystkie programy w zakresie zostały sprawdzone). Sprawdziłem tylko usunięcia z pierwszej kopii programu, ponieważ jest całkiem oczywiste, że usunięcie z innych kopii nie może zrobić więcej.
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
. Myślę, że byłoby to idealne rozwiązanie dla tego rodzaju wyzwań!