Perl, 1116 - 1124 Bytes, n = 3, Score = 1124 ^ (2/3) oder ungefähr 108,1
Update : Ich habe jetzt überprüft, dass dies mit n = 3 über Brute Force funktioniert (was ein paar Tage gedauert hat); Mit einem Programm dieser Komplexität ist es schwierig, die Strahlungsbeständigkeit von Hand zu überprüfen (und ich habe in einer früheren Version einen Fehler gemacht, weshalb die Byteanzahl gestiegen ist). Update beenden
Ich empfehle, stderr an einen Ort umzuleiten, an dem Sie es nicht sehen. Dieses Programm gibt eine Menge Warnungen über zweifelhafte Syntax aus, auch wenn Sie keine Zeichen daraus löschen.
Möglicherweise kann das Programm gekürzt werden. Daran zu arbeiten ist ziemlich mühsam und macht es leicht, mögliche Mikrooptimierungen zu übersehen. Ich war hauptsächlich bestrebt, die Anzahl der löschbaren Zeichen so hoch wie möglich zu halten (da dies der wirklich herausfordernde Teil des Programms ist) und behandelte den Code-Golf- Tiebreak als etwas, auf das ich gerne zielte, aber als etwas, das ich nicht setzen würde lächerlicher Optimierungsaufwand (aufgrund der Tatsache, dass es sehr leicht ist, den Strahlungswiderstand aus Versehen zu brechen).
Das Programm
Hinweis: _
Unmittelbar vor jedem der vier Vorkommen von steht ein literales Steuerzeichen (ASCII 31) -+
. Ich denke nicht, dass es korrekt in StackOverflow kopiert und eingefügt wurde, daher müssen Sie es erneut hinzufügen, bevor Sie das Programm ausführen.
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)####>####>####>####>####>####>
;
;
;
;
Die Erklärung
Dieses Programm besteht ganz klar aus vier identischen kleineren Programmen, die zusammen verkettet sind. Die Grundidee ist, dass jede Kopie des Programms überprüft, ob es zu stark beschädigt wurde, um ausgeführt zu werden oder nicht. Wenn dies der Fall ist, wird nichts unternommen (außer möglicherweise Warnungen auszusprechen) und die nächste Kopie ausgeführt. Wenn dies nicht der Fall ist (dh keine Löschungen oder das Zeichen, das gelöscht wurde, hat keinen Einfluss auf die Funktionsweise des Programms), erledigt es seine Kleinigkeit (druckt den Quellcode des gesamten Programms aus; wobei jeder Teil eine Kodierung des gesamten Quellcodes enthält) und dann beendet wird (um zu verhindern, dass andere unbeschädigte Kopien den Quellcode erneut ausdrucken und so das Quine ruinieren, indem zu viel Text gedruckt wird).
Jedes Teil besteht wiederum aus zwei Teilen, die praktisch unabhängig voneinander funktionieren. ein äußerer Wrapper und ein interner Code. Als solche können wir sie separat betrachten.
Außenverpackung
Der äußere Umbruch besteht im Grunde genommen aus eval<+eval<+eval< ... >####>####...>###
(plus ein paar Semikolons und Zeilenumbrüchen, deren Zweck ziemlich offensichtlich sein sollte), um sicherzustellen, dass die Teile des Programms getrennt bleiben, unabhängig davon, ob einige der Semikolons oder die Zeilenumbrüche vor ihnen gelöscht werden ). Das mag ziemlich einfach aussehen, ist aber in vielerlei Hinsicht subtil und der Grund, warum ich Perl für diese Herausforderung ausgewählt habe.
Schauen wir uns zunächst an, wie der Wrapper in einer unbeschädigten Kopie des Programms funktioniert. eval
Analysiert als integrierte Funktion, die ein Argument akzeptiert. Da ein Streit erwartet wird, handelt es sich +
hier um einen Unary +
(der Perl-Golfern inzwischen sehr vertraut ist; sie kommen überraschend oft als nützlich heraus). Wir erwarten immer noch ein Argument (wir haben gerade einen unären Operator gesehen), daher wird der <
nächste als Beginn des <>
Operators interpretiert (der keine Präfix- oder Postfix-Argumente akzeptiert und daher an der Operandenposition verwendet werden kann).
<>
ist ein ziemlich komischer Operator. Der übliche Zweck besteht darin, Dateihandles zu lesen und den Namen des Dateihandles in spitze Klammern zu setzen. Wenn der Ausdruck als Dateiname nicht gültig ist, führt er alternativ einen Globbing-Vorgang aus (im Grunde derselbe Prozess, den UNIX-Shells verwenden, um vom Benutzer eingegebenen Text in eine Folge von Befehlszeilenargumenten zu übersetzen). Viel ältere Versionen von Perl werden tatsächlich verwendet die Shell dafür, aber heutzutage handhabt Perl das Globbing intern). Die beabsichtigte Verwendung ist daher in etwa so <*.c>
, wie dies normalerweise bei einer Liste der Fall ist ("foo.c", "bar.c")
. In einem skalaren Kontext (wie dem Argument zueval
), gibt es nur den ersten Eintrag zurück, den es beim ersten Ausführen findet (das Äquivalent des ersten Arguments), und würde andere Einträge bei hypothetischen zukünftigen Läufen zurückgeben, die niemals vorkommen.
Heutzutage verarbeiten Shells häufig Befehlszeilenargumente. Wenn Sie so etwas wie -r
ohne Argumente angeben, wird es nur wörtlich an das Programm übergeben, unabhängig davon, ob es eine Datei mit diesem Namen gibt oder nicht. Perl verhält sich genauso, solange wir sicherstellen, dass zwischen dem <
und dem Matching keine speziellen Zeichen für die Shell oder für Perl vorhanden sind >
, können wir dies effektiv wie eine wirklich umständliche Form von String-Literalen verwenden. Noch besser ist, dass Perls Parser für zitatähnliche Operatoren zwangsweise dazu neigt, Klammern auch in Kontexten wie diesem zuzuordnen, in denen dies keinen Sinn macht, damit wir <>
sicher nisten können (was die Entdeckung ist, die für dieses Programm erforderlich ist). Der Hauptnachteil all dieser verschachtelten Elemente <>
besteht darin, dass sie dem Inhalt der Datei entkommen<>
ist fast unmöglich; Es scheint, als ob es zwei Schichten mit jeweils einem Ausrutscher gibt. Um <>
also etwas auf der Innenseite aller drei Schichten zu entkommen, müssen 63 Backslashes vorangestellt werden. Ich entschied, dass, obwohl die Codegröße bei diesem Problem nur eine untergeordnete Rolle spielt, es sich mit ziemlicher Sicherheit nicht lohnte, diese Art von Strafe für meine Punktzahl zu zahlen, und entschloss mich daher, den Rest des Programms ohne Verwendung der beleidigenden Zeichen zu schreiben.
Was passiert also, wenn Teile des Wrappers gelöscht werden?
- Deletionen in dem Wort
eval
führen dazu, dass es sich in ein Barewort verwandelt , eine Zeichenfolge ohne Bedeutung. Perl mag diese nicht, aber es behandelt sie so, als wären sie von Anführungszeichen umgeben. so eal<+eval<+...
wird interpretiert als"eal" < +eval<+...
. Dies hat keine Auswirkung auf die Funktionsweise des Programms, da es im Grunde genommen nur das Ergebnis der stark verschachtelten Auswertungen (die wir sowieso nicht verwenden) nimmt, in eine Ganzzahl umwandelt und einige sinnlose Vergleiche anstellt. (Diese Art von Dingen verursacht eine Menge Spam-Warnungen, da dies unter normalen Umständen offensichtlich nicht sinnvoll ist. Wir verwenden sie nur, um Löschungen zu absorbieren.) Dies ändert die Anzahl der benötigten schließenden spitzen Klammern (weil die öffnende Klammer) wird jetzt stattdessen als Vergleichsoperator interpretiert), aber die Kette von Kommentaren am Ende stellt sicher, dass die Zeichenfolge sicher endet, unabhängig davon, wie oft sie verschachtelt ist. (Es gibt hier mehr #
Zeichen als unbedingt erforderlich. Ich habe es so geschrieben, wie ich es getan habe, um das Programm komprimierbarer zu machen, sodass ich weniger Daten zum Speichern des Quines verwenden kann.)
- Wenn a
<
gelöscht wird, wird der Code jetzt analysiert als eval(eval<...>)
. Die sekundäre Funktion außerhalb eval
hat keine Auswirkung, da die Programme, die wir auswerten, nichts zurückgeben, was echte Auswirkungen als Programm hat Ausnahme, die eval
zur Rückgabe einer Nullzeichenfolge oder exit
zur Vermeidung einer Rückgabe führt).
- Wenn ein
+
Code gelöscht wird, hat dies keine unmittelbare Auswirkung, wenn der nebenstehende Code intakt ist. unary +
hat keine Auswirkung auf das Programm. (Der Grund, warum die Originale vorhanden +
sind, besteht darin, dass sie zur Reparatur von Schäden beitragen. Sie erhöhen die Anzahl der Situationen, in denen <
sie als unärer <>
und nicht als relationaler Operator interpretiert werden. Dies bedeutet, dass Sie mehr Löschvorgänge benötigen, um ein ungültiges Programm zu erstellen.)
Der Wrapper kann durch genügend Löschvorgänge beschädigt werden. Sie müssen jedoch eine Reihe von Löschvorgängen ausführen, um etwas zu erzeugen, das nicht analysiert werden kann. Mit vier Löschungen können Sie dies tun:
eal<evl<eval+<...
und in Perl ist der Vergleichsoperator nicht <
assoziativ, und daher wird ein Syntaxfehler angezeigt (derselbe, den Sie auch erhalten würden 1<2<3
). Daher ist die Obergrenze für das geschriebene Programm n = 3. Das Hinzufügen weiterer unärer +
s scheint eine vielversprechende Möglichkeit zu sein, um die Anzahl zu erhöhen. Da dies jedoch zu einer zunehmenden Wahrscheinlichkeit führen würde, dass auch das Innere des Wrappers beschädigt wird, kann die Überprüfung der Funktionsfähigkeit der neuen Version des Programms sehr schwierig sein.
Der Grund, warum der Wrapper so wertvoll ist, besteht darin, dass eval
in Perl Ausnahmen abgefangen werden, z. B. die Ausnahme, die beim Kompilieren eines Syntaxfehlers auftritt. Da es eval
sich um ein Zeichenfolgenliteral handelt, erfolgt die Kompilierung der Zeichenfolge zur Laufzeit. Wenn das Literal nicht kompiliert werden kann, wird die resultierende Ausnahme abgefangen. Dies führt eval
dazu, dass eine Nullzeichenfolge zurückgegeben und der Fehlerindikator gesetzt wird. Dies $@
wird jedoch auch nie überprüft (außer durch gelegentliches Ausführen der zurückgegebenen Nullzeichenfolge in einigen mutierten Versionen des Programms). Entscheidend ist, dass, wenn etwas mit dem Code im Inneren passieren sollteWenn der Wrapper einen Syntaxfehler verursacht, führt der Wrapper lediglich dazu, dass der Code nichts unternimmt (und das Programm wird weiter ausgeführt, um eine unbeschädigte Kopie von sich selbst zu finden). Daher muss der interne Code nicht annähernd so strahlungssicher sein wie der Wrapper. Alles, was uns interessiert, ist, dass wenn es beschädigt wird, es entweder identisch mit der unbeschädigten Version des Programms agiert oder abstürzt ( eval
um die Ausnahme abzufangen und fortzufahren) oder normal beendet wird, ohne etwas zu drucken.
In der Hülle
Der Code im Wrapper sieht im Grunde so aus (wieder gibt es ein Control _
, das Stack Exchange nicht sofort vor dem anzeigt -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Dieser Code ist vollständig mit glob-sicheren Zeichen geschrieben. Er soll ein neues Alphabet aus Interpunktionszeichen hinzufügen, das es ermöglicht, ein echtes Programm zu schreiben, indem ein String-Literal transliteriert und ausgewertet wird (wir können '
oder nicht "
als Anführungszeichen verwenden) marks, aber q(
... )
ist auch eine gültige Möglichkeit, in Perl einen String zu bilden. (Der Grund für das nicht druckbare Zeichen ist, dass wir etwas in das Leerzeichen ohne ein Leerzeichen im Programm umwandeln müssen. Daher bilden wir einen Bereich, der mit ASCII 31 beginnt, und erfassen das Leerzeichen als zweites Element des Bereichs.) Natürlich, wenn wir einige Zeichen über Umschrift sind produzieren, müssen wir Zeichen opfern , sie zu transkribieren ausAber Großbuchstaben sind nicht sehr nützlich und es ist viel einfacher, ohne Zugriff auf diese zu schreiben, als ohne Zugriff auf Interpunktionszeichen.
Hier ist das Alphabet mit Interpunktionszeichen, die als Ergebnis des Globus verfügbar werden (die obere Zeile zeigt die Codierung, die untere Zeile das Zeichen, das codiert wird):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Vor allem haben wir eine Reihe von Interpunktionszeichen, die nicht glob-sicher sind, aber beim Schreiben von Perl-Programmen zusammen mit dem Leerzeichen nützlich sind. Ich habe auch zwei Großbuchstaben gespeichert, das Literal A
und Z
(die nicht für sich selbst, sondern für T
und codieren U
, da dies A
sowohl als Endpunkt für den oberen als auch für den unteren Bereich benötigt wurde). Dies ermöglicht es uns, die Transliterationsanweisung selbst unter Verwendung des neuen codierten Zeichensatzes zu schreiben (obwohl Großbuchstaben nicht so nützlich sind, sind sie nützlich, um Änderungen an den Großbuchstaben festzulegen). Die bemerkenswertesten Zeichen, die wir nicht zur Verfügung haben, sind [
, \
und ]
, aber keine werden benötigt (als ich eine neue Zeile in der Ausgabe brauchte, habe ich sie mit der impliziten Zeile von erzeugtsay
anstatt schreiben zu müssen \n
; chr 10
hätte auch geklappt ist aber ausführlicher).
Wie üblich müssen wir uns Gedanken machen, was passiert, wenn das Innere des Wrappers außerhalb des String-Literal beschädigt wird. Ein korrupter eval
verhindert, dass etwas läuft; Damit sind wir einverstanden. Wenn die Anführungszeichen beschädigt werden, ist das Innere der Zeichenfolge ungültig, und der Wrapper fängt sie ab (und die zahlreichen Subtraktionen für Zeichenfolgen bedeuten, dass sie selbst dann nichts bewirken würden, wenn Sie sie als gültige Perl-Zeichenfolge festlegen würden ist ein akzeptables Ergebnis). Schäden an der Umschrift, wenn es nicht ein Syntaxfehler ist, wird mangle die Zeichenfolge ausgewertet wird, in der Regel verursacht es einen Syntaxfehler zu werden; Ich bin nicht zu 100% sicher, dass es keine Fälle gibt, in denen dies bricht, aber ich bin mir im Moment sicher, dass es einfach genug sein sollte, es zu beheben, wenn es solche Fälle gibt.
Das verschlüsselte Programm
Wenn wir in das String-Literal schauen, die von mir verwendete Codierung umkehren und Leerzeichen hinzufügen, um die Lesbarkeit zu verbessern, erhalten wir Folgendes (stellen Sie sich erneut einen Kontroll-Unterstrich vor dem vor -+
, der wie folgt codiert ist 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
Menschen, die an Quines gewöhnt sind, werden diese allgemeine Struktur erkennen. Der wichtigste Teil ist zu Beginn, wo wir überprüfen, ob $ o unbeschädigt ist. wenn Zeichen gelöscht wurden, wird seine Länge nicht überein 181
, so dass wir laufen , zzzz((()))
das, wenn es nicht ein Syntaxfehler ist auf nicht angepasste Klammern wird ein Laufzeitfehler, auch wenn Sie alle drei Zeichen löschen, weil keiner von zzzz
, zzz
, zz
, und z
ist eine Funktion, und es gibt keine Möglichkeit, das Parsen als eine andere Funktion zu verhindern, als das Löschen (((
und Verursachen eines offensichtlichen Syntaxfehlers. Der Scheck selbst ist außerdem unempfindlich gegen Beschädigungen. Das ||
kann beschädigt werden, |
aber das führt dazu, dass der zzzz((()))
Anruf bedingungslos ausgeführt wird. Wenn Sie Variablen oder Konstanten beschädigen, führt dies zu einer Nichtübereinstimmung, da Sie Folgendes vergleichen 0
:180
, 179
, 178
Für die Gleichstellung zu einem gewissen Teil der Ziffern 181
; Wenn Sie eins entfernen, =
führt dies zu einem Analysefehler, und zwei führen =
zwangsläufig dazu, dass die LHS entweder als Ganzzahl 0 oder als Null-Zeichenfolge ausgewertet wird, die beide falsch sind.
Update : Diese Überprüfung war in einer früheren Version des Programms etwas falsch, daher musste ich sie bearbeiten, um das Problem zu beheben. Die vorherige Version sah nach der Dekodierung so aus:
length$o==179||zzzz((()))
und es war möglich, die ersten drei Satzzeichen zu löschen, um dies zu erhalten:
lengtho179||zzz((()))
lengtho179
ein Barwort zu sein, ist wahr und bricht den Scheck. Ich habe dies durch Hinzufügen von zwei zusätzlichen B
Zeichen (die Leerzeichen codieren) behoben , was bedeutet, dass die neueste Version des Quine dies tut:
length$o ==181||zzzz((()))
Jetzt ist es unmöglich, sowohl die =
Zeichen als auch das $
Zeichen zu verbergen , ohne einen Syntaxfehler zu erzeugen. (Ich hatte zwei Räume hinzufügen , anstatt ein , weil eine Länge von 180
einem wörtlichen setzen würde 0
Charakter in die Quelle, die in diesem Zusammenhang auf ganzzahlige-Vergleich Null mit einem Bareword missbraucht werden könnte, was erfolgreich ist .) End - Update
Sobald die Längenprüfung bestanden ist, wissen wir, dass die Kopie unbeschädigt ist, zumindest was die Löschung von Zeichen betrifft. Von daher ist das Quining einfach (das Ersetzen von Satzzeichen aufgrund einer beschädigten Dekodierungstabelle würde bei dieser Prüfung nicht erfasst , aber ich habe bereits über Brute-Forcing , dass keine drei Deletionen von bestätigten nur vermutlich die meisten von ihnen Ursache Syntaxfehler), die Dekodierungstabelle brechen die quine. Wir haben $o
bereits eine Variable, also müssen wir nur die äußeren Wrapper fest codieren (mit einigem Komprimierungsgrad; ich habe den Code-Golf- Teil der Frage nicht ganz übersprungen ). Ein Trick ist, dass wir den Großteil der Codierungstabelle in speichern$r
; Entweder können wir es buchstäblich drucken, um den Codierungstabellenabschnitt des inneren Wrappers zu generieren, oder wir können Code um ihn herum verketten und eval
den Decodierungsprozess in umgekehrter Reihenfolge ausführen (um herauszufinden, wie die codierte Version von $ o lautet) (nur die decodierte Version ist zu diesem Zeitpunkt verfügbar).
Wenn wir eine intakte Kopie wären und somit das gesamte Originalprogramm ausgeben könnten, rufen wir exit
auf, um zu verhindern, dass die anderen Kopien ebenfalls versuchen, das Programm auszudrucken.
Überprüfungsskript
Nicht sehr hübsch, aber es zu posten, weil jemand gefragt hat. Ich habe dies mehrere Male mit einer Vielzahl von Einstellungen ausgeführt (normalerweise geändert $min
und $max
auf verschiedene Bereiche von Interesse überprüft). Es war kein vollautomatischer Prozess. Es hat die Tendenz, aufgrund der hohen CPU-Belastung an anderer Stelle nicht mehr zu laufen. In diesem Fall habe ich nur $min
den ersten Wert geändert , der $x
nicht vollständig überprüft wurde, und das Skript weiter ausgeführt (um sicherzustellen, dass alle Programme im Bereich schließlich überprüft wurden). Ich habe nur Löschungen von der ersten Kopie des Programms überprüft, da es ziemlich offensichtlich ist, dass Löschungen von den anderen Kopien nicht mehr können.
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
. Ich denke, es wäre ideal für diese Art von Herausforderung!