Perl, 1116 1124 byte, n = 3, punteggio = 1124 ^ (2/3) o circa 108,1
Aggiornamento : ora ho verificato che funziona con n = 3 tramite forza bruta (che ha richiesto un paio di giorni); con un programma così complesso, è difficile verificare manualmente la resistenza alle radiazioni (e ho fatto un errore in una versione precedente, motivo per cui il conteggio dei byte è aumentato). Termina aggiornamento
Consiglio di reindirizzare stderr da qualche parte per non vederlo; questo programma produce un sacco di avvertimenti sulla sintassi dubbia anche quando non si eliminano i caratteri da esso.
È possibile che il programma possa essere abbreviato. Lavorare su questo è abbastanza doloroso, rendendo facile perdere possibili micro-ottimizzazioni. Miravo principalmente a ottenere il numero di personaggi eliminabili il più alto possibile (perché questa è la parte davvero impegnativa del programma) e ho trattato il tiebreak del code-golf come qualcosa che era bello puntare ma come qualcosa che non avrei messo ridicolo sforzo di ottimizzazione (sulla base del fatto che è molto facile rompere la resistenza alle radiazioni per caso).
Il programma
Nota: c'è un letterale Control- _
character (ASCII 31) immediatamente prima di ciascuna delle quattro occorrenze di -+
. Non credo che sia stato copiato e incollato correttamente su StackOverflow, quindi dovrai aggiungerlo nuovamente prima di eseguire il programma.
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)####>####>####>####>####>####>
;
;
;
;
La spiegazione
Questo programma è, chiaramente, composto da quattro programmi identici più piccoli concatenati insieme. L'idea di base è che ogni copia del programma verificherà se è stata danneggiata troppo male per l'esecuzione o meno; se lo è stato, non farà nulla (tranne che eventualmente sputare avvisi) e consentirà l'esecuzione della copia successiva; se non lo è stato (ovvero nessuna eliminazione, o il carattere che è stato eliminato è stato uno che non fa alcuna differenza per il funzionamento del programma), farà la sua parte (stampare il codice sorgente del programma completo; questo è un vero quine, con ciascuna parte contenente una codifica dell'intero codice sorgente) e quindi uscire (impedendo a qualsiasi altra copia non danneggiata di stampare nuovamente il codice sorgente e rovinando così la riga stampando troppo testo).
Ogni parte è a sua volta composta da due parti che sono effettivamente funzionalmente indipendenti; un wrapper esterno e un codice interno. Pertanto, possiamo considerarli separatamente.
Involucro esterno
Il wrapper esterno è, fondamentalmente, eval<+eval<+eval< ... >####>####...>###
(più un gruppo di punti e virgola e di nuove righe il cui scopo dovrebbe essere piuttosto ovvio; è garantire che le parti del programma rimangano separate indipendentemente dal fatto che alcuni punti e virgola o le nuove righe prima di loro, vengano eliminati ). Potrebbe sembrare abbastanza semplice, ma è sottile in vari modi e il motivo per cui ho scelto Perl per questa sfida.
Innanzitutto, vediamo come funziona il wrapper in una copia non danneggiata del programma. eval
analizza come una funzione integrata, che accetta un argomento. Poiché è atteso un argomento, +
ecco un unario +
(che ormai sarà molto familiare ai golfisti del Perl; si rivelano utili sorprendentemente spesso). Stiamo ancora aspettando un argomento (abbiamo appena visto un operatore unario), quindi quello <
che viene dopo viene interpretato come l'inizio <>
dell'operatore (che non accetta argomenti prefisso o postfisso, e quindi può essere utilizzato in posizione di operando).
<>
è un operatore abbastanza strano. Il suo solito scopo è quello di leggere filehandle, e si posiziona il nome filehandle all'interno le parentesi angolari. In alternativa, se l'espressione non è valida come nome di filehandle, fa globbing (fondamentalmente, lo stesso processo utilizzato dalle shell UNIX per tradurre il testo immesso dall'utente in una sequenza di argomenti della riga di comando; in realtà sono state utilizzate versioni molto più vecchie di Perl il guscio per questo, ma al giorno d'oggi Perl gestisce i globbing internamente). L'uso previsto, pertanto, è sulla falsariga di <*.c>
, che in genere restituisce un elenco simile ("foo.c", "bar.c")
. In un contesto scalare (come l'argomento aeval
), restituisce solo la prima voce che trova la prima volta che viene eseguita (l'equivalente del primo argomento) e restituisce altre voci su ipotesi future esecuzioni che non si verificano mai.
Ora, le shell spesso gestiscono argomenti da riga di comando; se dai qualcosa del genere -r
senza argomenti, verrà semplicemente passato al programma alla lettera, indipendentemente dal fatto che esista o meno un file con quel nome. Perl agisce allo stesso modo, quindi finché ci assicuriamo che non ci siano caratteri speciali per la shell o per il Perl tra la <
e la corrispondenza >
, possiamo effettivamente usarlo come una forma davvero imbarazzante di stringa letterale. Ancora meglio, il parser di Perl per operatori simili a virgolette ha una tendenza compulsiva a far combaciare parentesi anche in contesti come questo dove non ha senso, quindi possiamo nidificare in <>
sicurezza (che è la scoperta necessaria affinché questo programma sia possibile). Il principale svantaggio di tutti questi nidificati <>
è che sfuggire al contenuto di<>
è quasi impossibile; sembra che ci siano due livelli di non-escape con ciascuno <>
, quindi per sfuggire a qualcosa all'interno di tutti e tre, deve essere preceduto da 63 barre rovesciate. Ho deciso che anche se la dimensione del codice è solo una considerazione secondaria in questo problema, quasi sicuramente non valeva la pena pagare questo tipo di penalità per il mio punteggio, quindi ho deciso di scrivere il resto del programma senza usare i caratteri offensivi.
Cosa succede se parti del wrapper vengono eliminate?
- Le eliminazioni nella parola fanno
eval
sì che si trasformi in una parola nuda , una stringa senza significato. Al Perl non piacciono questi, ma li tratta come se fossero circondati da virgolette; quindi eal<+eval<+...
viene interpretato come"eal" < +eval<+...
. Questo non ha alcun effetto sul funzionamento del programma, perché fondamentalmente sta solo prendendo il risultato dagli evalidi fortemente annidati (che non usiamo comunque), lanciandolo su un numero intero e facendo inutili confronti su di esso. (Questo genere di cose provoca molti avvisi di spam in quanto chiaramente non è una cosa utile da fare in circostanze normali; lo stiamo solo usando per assorbire le eliminazioni.) Questo cambia il numero di parentesi angolari di chiusura di cui abbiamo bisogno (perché la parentesi aperta viene ora interpretato come un operatore di confronto), ma la catena di commenti alla fine assicura che la stringa finisca in modo sicuro, indipendentemente da quante volte è nidificata. (Ci sono più #
segni di quelli strettamente necessari qui; l'ho scritto come ho fatto per rendere il programma più comprimibile, permettendomi di usare meno dati per memorizzare i quine.)
- Se un
<
viene eliminato, il codice ora analizza come eval(eval<...>)
. Il secondario, esterno, non eval
ha alcun effetto, perché i programmi che stiamo valutando non restituiscono nulla che abbia effetti reali come programma (se ritornano normalmente, è normalmente una stringa nulla o una parola nuda; più comunemente ritornano via eccezione, che causa la eval
restituzione di una stringa nulla o l'utilizzo exit
per evitare la restituzione).
- Se
+
viene eliminato, ciò non ha effetto immediato se il codice adiacente è intatto; unario +
non ha alcun effetto sul programma. (Il motivo degli originali +
è quello di aiutare a riparare i danni; aumentano il numero di situazioni in cui <
viene interpretato come unario <>
piuttosto che come un operatore relazionale, il che significa che sono necessarie più eliminazioni per produrre un programma non valido.)
Il wrapper può essere danneggiato con abbastanza eliminazioni, ma è necessario eseguire una serie di eliminazioni per produrre qualcosa che non analizza. Con quattro eliminazioni, puoi farlo:
eal<evl<eval+<...
e in Perl, l'operatore relazionale <
non è associativo e quindi si ottiene un errore di sintassi (lo stesso con cui si otterrebbe 1<2<3
). Pertanto, il limite per il programma come scritto è n = 3. L'aggiunta di più unari +
sembra un modo promettente per aumentarlo, ma poiché ciò renderebbe sempre più probabile che anche l'interno del wrapper possa rompersi, verificare che la nuova versione del programma funzioni potrebbe essere molto difficile.
Il motivo per cui il wrapper è così prezioso è che eval
in Perl vengono rilevate eccezioni, come (ad esempio) l'eccezione che si ottiene quando si tenta di compilare un errore di sintassi. Poiché si eval
tratta di una stringa letterale, la compilazione della stringa avviene in fase di esecuzione e se la letterale non riesce a compilare, l'eccezione risultante viene catturata. Ciò provoca la eval
restituzione di una stringa null e l'impostazione dell'indicatore di errore $@
, ma non controlliamo mai neanche (tranne eseguendo occasionalmente la stringa null restituita in alcune versioni mutate del programma). Fondamentalmente, questo significa che se qualcosa dovesse accadere al codice all'internoil wrapper, causando un errore di sintassi, quindi il wrapper farà semplicemente in modo che il codice non faccia nulla (e il programma continuerà a essere eseguito nel tentativo di trovare una copia non danneggiata di se stesso). Pertanto, il codice interno non deve essere quasi a prova di radiazione come il wrapper; tutto ciò che ci interessa è che se danneggiato, agirà in modo identico alla versione non modificata del programma, altrimenti si arresta in modo anomalo (consentendo eval
di rilevare l'eccezione e continuare) o di uscire normalmente senza stampare nulla.
All'interno della confezione
Il codice all'interno del wrapper, fondamentalmente, assomiglia a questo (di nuovo, c'è un controllo _
che Stack Exchange non mostrerà immediatamente prima del -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Questo codice è scritto interamente con caratteri glob-safe e il suo scopo è quello di aggiungere un nuovo alfabeto di segni di punteggiatura che consenta di scrivere un vero programma, attraverso la traslitterazione e la valutazione di una stringa letterale (non possiamo usare '
o "
come citazione segni, ma q(
... )
è anche un modo valido per formare una stringa in Perl). (La ragione del carattere non stampabile è che dobbiamo traslitterare qualcosa nel carattere spaziale senza un carattere letterale nello spazio nel programma; quindi formiamo un intervallo a partire da ASCII 31 e catturiamo lo spazio come secondo elemento dell'intervallo.) ovviamente, se stiamo producendo alcuni personaggi tramite traslitterazione, dobbiamo sacrificare caratteri di traslitterare loro da, ma le lettere maiuscole non sono molto utili ed è molto più facile scrivere senza accesso a quelle che senza accesso ai segni di punteggiatura.
Ecco l'alfabeto dei segni di punteggiatura che diventano disponibili come risultato del glob (la riga superiore mostra la codifica, la riga inferiore il carattere che codifica):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +;? <=> @ Azz {|} ~
In particolare, abbiamo un sacco di segni di punteggiatura che non sono globalmente sicuri ma sono utili nella scrittura di programmi Perl, insieme al carattere spaziale. Ho anche salvato due lettere maiuscole, il letterale A
e Z
(che codificano non per se stessi, ma per T
e U
, perché A
era necessario come endpoint superiore e inferiore); questo ci consente di scrivere le stesse istruzioni di traslitterazione utilizzando il nuovo set di caratteri codificati (sebbene le lettere maiuscole non siano così utili, sono utili per specificare le modifiche alle lettere maiuscole). I personaggi più importanti che non sono disponibili sono [
, \
e ]
, ma nessuno è necessario (quando avevo bisogno di una nuova riga nell'output, l'ho prodotta usando la nuova riga implicita dasay
piuttosto che dover scrivere \n
; chr 10
avrebbe anche funzionato ma è più dettagliato).
Come al solito, dobbiamo preoccuparci di cosa succede se l'interno dell'involucro viene danneggiato all'esterno della stringa letterale. Un corrotto eval
impedirà l'esecuzione di qualsiasi cosa; stiamo bene con quello. Se le virgolette vengono danneggiate, l'interno della stringa non è valido Perl, e quindi il wrapper lo catturerà (e le numerose sottrazioni sulle stringhe significano che anche se tu potessi renderlo valido Perl, non farebbe nulla, il che è un risultato accettabile). Danni al traslitterazione, se non è un errore di sintassi, storpiare la stringa in corso di valutazione, tipicamente causando esso diventi un errore di sintassi; Non sono sicuro al 100% che non ci siano casi in cui questo si rompa, ma al momento lo sto forzando bruscamente per assicurarmene, e dovrebbe essere abbastanza facile da risolvere se ci sono.
Il programma codificato
Guardando all'interno della stringa letterale, invertendo la codifica che ho usato e aggiungendo spazi bianchi per renderla più leggibile, otteniamo questo (di nuovo, immagina un controllo-trattino basso prima di -+
, che è codificato come 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
Le persone abituate ai quines riconosceranno questa struttura generale. La parte più cruciale è all'inizio, dove verifichiamo che $ o non sia danneggiato; se i caratteri sono stati cancellati, la sua lunghezza non corrisponde 181
, quindi corriamo zzzz((()))
il quale, se non è un errore di sintassi a causa di parentesi senza eguali, sarà un errore di runtime, anche se si elimina qualsiasi tre personaggi, perché nessuno di zzzz
, zzz
, zz
, ed z
è una funzione e non c'è modo di impedirne l'analisi come funzione se non quella di eliminare (((
e causare un errore di sintassi ovvia. Anche il controllo stesso è immune ai danni; la ||
possono essere danneggiati a |
ma che causerà la zzzz((()))
chiamata per eseguire senza condizioni; variabili o costanti dannose causeranno una discrepanza perché stai confrontando una delle 0
,180
, 179
, 178
Per l'uguaglianza a un sottoinsieme delle cifre di 181
; e rimuoverne uno =
causerà un errore di analisi e due =
causeranno inevitabilmente che LHS valuti un numero intero 0 o una stringa nulla, entrambi falsi.
Aggiornamento : questo controllo era leggermente sbagliato in una versione precedente del programma, quindi ho dovuto modificarlo per risolvere il problema. La versione precedente era simile al seguente dopo la decodifica:
length$o==179||zzzz((()))
ed è stato possibile eliminare i primi tre segni di punteggiatura per ottenere questo:
lengtho179||zzz((()))
lengtho179
, essendo una parola nuda, è veritiero e quindi interrompe il controllo. Ho risolto questo aggiungendo altri due B
caratteri (che codificano i caratteri dello spazio), il che significa che l'ultima versione della quine fa questo:
length$o ==181||zzzz((()))
Ora è impossibile nascondere sia i =
segni che il $
segno senza produrre un errore di sintassi. (Ho dovuto aggiungere due spazi anziché uno perché una lunghezza di 180
avrebbe messo un 0
carattere letterale nella fonte, che potrebbe essere abusato in questo contesto per confrontare zero intero con una parola nuda, che ha esito positivo.) Fine aggiornamento
Una volta superato il controllo di lunghezza, sappiamo che la copia non è danneggiata, almeno in termini di eliminazioni di caratteri da esso, quindi è tutto semplice smantellare da lì (sostituzioni di segni di punteggiatura a causa di una tabella di decodifica danneggiata non verrebbero colti da questo controllo , ma ho già verificato tramite brute-force che nessuna delezione dalla sola tabella di decodifica interrompe il quine; presumibilmente la maggior parte di essi causa errori di sintassi). Abbiamo $o
in una variabile già, quindi tutto quello che dobbiamo fare è hardcode gli involucri esterni (con qualche piccolo grado di compressione, non ho saltare fuori il codice di-golf parte della domanda del tutto ). Un trucco è che memorizziamo la maggior parte della tabella di codifica$r
; possiamo stamparlo letteralmente per generare la sezione della tabella di codifica del wrapper interno o concatenare un po 'di codice attorno ad esso e eval
per eseguire il processo di decodifica al contrario (permettendoci di capire qual è la versione codificata di $ o , avendo solo la versione decodificata disponibile a questo punto).
Infine, se fossimo una copia intatta e quindi potessimo produrre l'intero programma originale, chiamiamo exit
per evitare che anche le altre copie provino a stampare il programma.
Script di verifica
Non molto carino, ma pubblicandolo perché qualcuno ha chiesto. L'ho eseguito più volte con una varietà di impostazioni (in genere cambiando $min
e $max
per verificare varie aree di interesse); non è stato un processo completamente automatizzato. Ha la tendenza a smettere di funzionare a causa del pesante carico della CPU altrove; quando ciò è accaduto, sono appena passato $min
al primo valore $x
che non è stato completamente controllato e ho continuato a eseguire lo script (assicurando così che tutti i programmi nell'intervallo siano stati controllati alla fine). Ho controllato solo le eliminazioni dalla prima copia del programma, perché è abbastanza ovvio che le eliminazioni dalle altre copie non possono fare di più.
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
. Penso che sarebbe l'ideale per questo tipo di sfida!