Perl, 1116 1124 octets, n = 3, score = 1124 ^ (2/3) ou environ 108,1
Mise à jour : j'ai maintenant vérifié que cela fonctionne avec n = 3 via la force brute (ce qui a pris quelques jours); Avec un programme aussi complexe, il est difficile de vérifier manuellement la résistance aux radiations (et j'ai commis une erreur dans une version précédente, c'est pourquoi le nombre d'octets a augmenté). Fin de la mise à jour
Je recommande de rediriger stderr quelque part que vous ne le verrez pas; ce programme produit une tonne d'avertissements sur la syntaxe douteuse même lorsque vous ne supprimez pas de caractères de celle-ci.
Il est possible que le programme puisse être raccourci. Travailler dessus est assez pénible, ce qui permet de rater facilement les micro-optimisations possibles. Je visais surtout à obtenir le plus grand nombre possible de caractères supprimables (car c’est là la partie la plus difficile du programme), et j’ai considéré la rupture de code-golf comme un objectif agréable à atteindre, mais que je ne mettrais pas. effort ridicule d’optimisation (sur la base du fait qu’il est très facile de casser la résistance aux radiations par accident).
Le programme
Remarque: il y a un _
caractère de contrôle littéral (ASCII 31) immédiatement avant chacune des quatre occurrences de -+
. Je ne pense pas qu'il soit correctement copié-collé sur StackOverflow, vous devrez donc le rajouter avant d'exécuter le programme.
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)####>####>####>####>####>####>
;
;
;
;
L'explication
Clairement, ce programme est constitué de quatre programmes identiques plus petits concaténés ensemble. L'idée de base est que chaque copie du programme vérifie si elle a été trop endommagée pour être exécutée ou non. si cela a été le cas, il ne fera rien (à part peut-être émettre des avertissements) et laissera la copie suivante être exécutée; s'il ne l'a pas été (c.-à-d. aucune suppression, ou si le caractère qui a été supprimé ne fait aucune différence dans le fonctionnement du programme), il fera son petit geste (afficher le code source du programme complet; c'est un bon quine, avec chaque partie contenant un codage du code source complet) puis quittez (en empêchant toute autre copie non endommagée d’imprimer à nouveau le code source et de ruiner ainsi la séquence en imprimant trop de texte).
Chaque partie est à son tour composée de deux parties qui sont effectivement indépendantes sur le plan fonctionnel; un wrapper extérieur et un code interne. En tant que tels, nous pouvons les considérer séparément.
Emballage extérieur
Le wrapper extérieur est, en gros, eval<+eval<+eval< ... >####>####...>###
(plus un groupe de points-virgules et de nouvelles lignes dont le but doit être assez évident; il s'agit de s'assurer que les parties du programme resteront séparées, que les sémicules ou les nouvelles lignes qui les précèdent soient supprimés. ). Cela peut paraître assez simple, mais c'est subtile de plusieurs façons et c'est la raison pour laquelle j'ai choisi Perl pour ce défi.
Tout d’abord, examinons le fonctionnement du wrapper dans une copie non endommagée du programme. eval
analyse en tant que fonction intégrée, qui prend un argument. Parce qu'un argument est attendu, +
voici un unaire +
(qui sera maintenant très familier aux golfeurs de Perl; ils sont d'une utilité surprenante souvent). Nous attendons toujours un argument (nous venons de voir un opérateur unaire), donc le <
qui vient ensuite est interprété comme le début de l' <>
opérateur (qui ne prend pas d'argument préfixe ou postfix, et peut donc être utilisé en position d'opérande).
<>
est un opérateur assez bizarre. Son but est d' habitude de lire handles de fichiers, et vous placez le nom filehandle dans les crochets. Sinon, si l'expression n'est pas valide en tant que nom de descripteur de fichier, elle est globalisée (processus identique à celui utilisé par les shells UNIX pour traduire le texte entré par l'utilisateur en une séquence d'arguments de ligne de commande; des versions bien plus anciennes de Perl étaient effectivement utilisées). le shell pour cela, mais de nos jours Perl gère le globbing en interne). L’utilisation prévue va donc dans le sens de <*.c>
, qui devrait normalement renvoyer une liste semblable à ("foo.c", "bar.c")
. Dans un contexte scalaire (tel que l’argument deeval
), il retourne simplement la première entrée trouvée lors de la première exécution (l'équivalent du premier argument) et renvoie d'autres entrées sur des exécutions hypothétiques futures qui ne se produisent jamais.
Maintenant, les shells gèrent souvent des arguments en ligne de commande; si vous donnez quelque chose comme -r
sans argument, il sera simplement transmis intégralement au programme, qu'il y ait ou non un fichier portant ce nom. Perl agit de la même manière. Par conséquent, tant que nous nous assurons qu'il n'y a pas de caractères spéciaux pour le shell ou entre Perl <
et les correspondances >
, nous pouvons efficacement utiliser ceci comme une forme vraiment maladroite de littéral de chaîne. Mieux encore, l'analyseur Perl pour les opérateurs de type devis a une tendance compulsive à faire correspondre les crochets même dans des contextes comme celui-ci où cela n'a aucun sens, afin que nous puissions imbriquer en <>
toute sécurité (ce qui est la découverte nécessaire pour que ce programme soit possible). Le principal inconvénient de tous ces éléments imbriqués <>
est que l’échappement du contenu de la<>
est presque impossible; il semble y avoir deux couches de fuites avec chacune <>
, donc pour échapper à quelque chose à l'intérieur des trois, il faut le faire précéder de 63 barres obliques inverses. J'ai décidé que même si la taille du code n'était qu'une considération secondaire dans ce problème, cela ne valait certainement pas la peine de payer ce type de pénalité à mon score, alors j'ai simplement décidé d'écrire le reste du programme sans utiliser les caractères incriminés.
Alors que se passe-t-il si des parties de l'emballage sont supprimées?
- Les suppressions dans le mot
eval
font qu'il se transforme en mot simple , une chaîne sans signification. Perl n'aime pas cela, mais il les traite comme s'ils étaient entourés de guillemets. eal<+eval<+...
est donc interprété comme"eal" < +eval<+...
. Cela n'a aucun effet sur le fonctionnement du programme, car il ne prend en fait que le résultat des évaluations fortement imbriquées (que nous n'utilisons pas de toute façon), en le convertissant en un entier et en effectuant des comparaisons inutiles. (Ce type de produit génère de nombreux spams de mise en garde car ce n’est manifestement pas une chose utile à faire dans des circonstances normales; nous l’utilisons simplement pour absorber les suppressions.) Cela change le nombre de crochets dont nous avons besoin (parce que le est maintenant interprété comme un opérateur de comparaison), mais la chaîne de commentaires à la fin garantit que la chaîne se terminera en toute sécurité, quel que soit le nombre de fois où elle est imbriquée. (Il y a plus de #
signes que strictement nécessaire ici; je l'ai écrit comme je l'ai fait afin de rendre le programme plus compressible, me permettant d'utiliser moins de données pour stocker le quine.)
- Si un
<
est supprimé, le code est analysé comme eval(eval<...>)
. L'extérieur, secondaire eval
n'a aucun effet, car les programmes que nous évaluons ne renvoient aucun élément ayant des effets réels en tant que programme (s'ils retournent normalement, c'est normalement une chaîne nulle ou un mot simple; plus généralement, ils retournent via exception, ce qui a eval
pour effet de renvoyer une chaîne null ou de l’utiliser exit
pour éviter de tout renvoyer).
- Si a
+
est supprimé, cela n’a pas d’effet immédiat si le code adjacent est intact; unaire +
n'a aucun effet sur le programme. (Les raisons d'origine +
existent pour aider à réparer les dommages; elles augmentent le nombre de situations dans lesquelles un <
interprète est considéré comme unaire, <>
plutôt que comme un opérateur relationnel, ce qui signifie que vous avez besoin de plus de suppressions pour générer un programme invalide.)
L'empaquetage peut être endommagé avec suffisamment de suppressions, mais vous devez effectuer une série de suppressions afin de produire quelque chose qui n'est pas analysé. Avec quatre suppressions, vous pouvez faire ceci:
eal<evl<eval+<...
et en Perl, l'opérateur relationnel <
n'est pas associatif et vous obtenez donc une erreur de syntaxe (identique à celle que vous obtiendriez 1<2<3
). En tant que tel, le plafond du programme écrit est n = 3. Ajouter plus de unaires +
semble un moyen prometteur de l’augmenter, mais comme cela rendrait de plus en plus probable que l’intérieur de l’emballage puisse aussi se briser, il peut être très difficile de vérifier que la nouvelle version du programme fonctionne.
La raison pour laquelle l'encapsuleur est si précieux est que eval
Perl intercepte des exceptions, telles que (par exemple) l'exception que vous obtenez lorsque vous essayez de compiler une erreur de syntaxe. Comme il eval
s'agit d'un littéral de chaîne, la compilation de la chaîne a lieu au moment de l'exécution et si la compilation échoue, l'exception qui en résulte est interceptée. Cela a eval
pour effet de renvoyer une chaîne null et de définir l'indicateur d'erreur $@
, mais nous ne vérifions jamais non plus (sauf en exécutant occasionnellement la chaîne null renvoyée dans quelques versions mutées du programme). Surtout, cela signifie que si quelque chose devait arriver au code à l' intérieurle wrapper, causant une erreur de syntaxe, il ne fera que rendre le code inactif (et le programme continuera à s'exécuter pour tenter de trouver une copie non endommagée de lui-même). Par conséquent, le code interne ne doit pas nécessairement être aussi résistant aux radiations que l’emballage; tout ce qui nous importe est que s'il est endommagé, il agira de la même manière que la version non endommagée du programme, ou bien il plantera (permettant eval
d'attraper l'exception et de continuer) ou quittera normalement sans imprimer.
À l'intérieur de l'emballage
Le code à l'intérieur du wrapper, fondamentalement, ressemble à ceci (là encore, il y a un contrôle - _
que Stack Exchange ne montrera pas immédiatement avant le -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Ce code est entièrement écrit avec des caractères globales, et vise à ajouter un nouvel alphabet de signes de ponctuation permettant d'écrire un programme réel, en effectuant une translittération et en évaluant un littéral de chaîne (nous ne pouvons ni utiliser '
ni "
citer marques, mais q(
… )
est également un moyen valide de former une chaîne en Perl). (La raison du caractère non imprimable est que nous devons translittérer quelque chose dans le caractère espace sans caractère littéral dans le programme; nous formons ainsi une plage commençant à ASCII 31 et l'interceptons comme deuxième élément de la plage.) Évidemment, si nous produisons des caractères par translittération, nous devons sacrifier des caractères pour les translittérer de, mais les lettres majuscules ne sont pas très utiles et il est beaucoup plus facile d’écrire sans accès à celles-ci que sans accès aux signes de ponctuation.
Voici l'alphabet des signes de ponctuation qui deviennent disponibles à la suite du glob (la ligne supérieure indique l'encodage, la ligne inférieure le caractère qu'il code):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Plus particulièrement, nous avons un tas de signes de ponctuation qui ne sont pas globalement sûrs mais qui sont utiles pour écrire des programmes Perl, ainsi que le caractère espace. J'ai également enregistré deux lettres majuscules, le littéral A
et Z
(qui ne codent pas pour elles-mêmes, mais pour T
et U
, car elles A
étaient nécessaires en tant que point final supérieur et inférieur); cela nous permet d'écrire l'instruction de translittération elle-même en utilisant le nouveau jeu de caractères codés (bien que les lettres majuscules ne soient pas très utiles, elles sont utiles pour spécifier les modifications à apporter aux lettres majuscules). Les caractères les plus remarquables dont nous ne disposons pas sont [
, \
et ]
, mais aucun n’est nécessaire (lorsque j’avais besoin d’une nouvelle ligne dans la sortie, je l’ai produite à l’aide de la nouvelle ligne implicite desay
plutôt que d'avoir besoin d'écrire \n
; chr 10
aurait également fonctionné mais est plus prolixe).
Comme d'habitude, nous devons nous inquiéter de ce qui se passe si l'intérieur du wrapper est endommagé en dehors du littéral string. Un corrompu eval
empêchera tout en cours d'exécution; cela nous convient. Si les guillemets sont endommagés, l’intérieur de la chaîne n’est pas valide en Perl, et donc le wrapper l’attrapera (et les nombreuses soustractions sur les chaînes signifient que même si vous pouviez le rendre valide en Perl, il ne ferait rien, ce qui est un résultat acceptable). Si la translittération n’est pas une erreur de syntaxe, la chaîne en cours d’évaluation sera mutilée, ce qui en fera généralement une erreur de syntaxe; Je ne suis pas tout à fait sûr qu'il n'y ait pas de cas dans lequel cela se casse, mais je le force brute pour le moment, mais ce devrait être assez facile à réparer s'il y en a.
Le programme encodé
En regardant à l'intérieur du littéral de chaîne, en inversant l'encodage que j'ai utilisé et en ajoutant des espaces pour le rendre plus lisible, nous obtenons ceci (encore une fois, imaginons un contrôle-soulignement avant le -+
, codé ainsi 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
Les personnes habituées à quines reconnaîtront cette structure générale. La partie la plus cruciale est au début, où nous vérifions que $ o n’est pas endommagé; si les caractères ont été supprimés, sa longueur ne correspondra pas à 181
, donc nous courons zzzz((()))
qui, si elle n'est pas une erreur de syntaxe en raison de supports inégalés, sera une erreur d'exécution même si vous supprimez tous les trois personnages, parce qu'aucun de zzzz
, zzz
, zz
, et z
est une fonction, et il n’ya aucun moyen de l’empêcher de l’analyser comme une fonction autre que la suppression (((
et la génération d’une erreur de syntaxe évidente. Le chèque lui-même est également immunisé contre les dommages; cela ||
peut être endommagé, |
mais l' zzzz((()))
appel sera exécuté sans condition; les variables ou les constantes préjudiciables causeront une disparité, car vous comparez l’un des éléments suivants 0
,180
, 179
, 178
Pour l' égalité à un sous - ensemble des chiffres 181
; et en supprimer un =
provoquera un échec d'analyse, et deux =
obligeront inévitablement le LHS à se transformer en un entier égal à 0 ou en une chaîne nulle, les deux étant falsey.
Mise à jour : Cette vérification était légèrement fausse dans une version précédente du programme, j'ai donc dû la modifier pour résoudre le problème. La version précédente ressemblait à ceci après le décodage:
length$o==179||zzzz((()))
et il était possible de supprimer les trois premiers signes de ponctuation pour obtenir ceci:
lengtho179||zzz((()))
lengtho179
, étant un mot simple, est la vérité et brise ainsi la vérification. J'ai corrigé cela en ajoutant deux B
caractères supplémentaires (qui codent les espaces), ce qui signifie que la dernière version de la quine fait ceci:
length$o ==181||zzzz((()))
Maintenant, il est impossible de cacher les =
signes et le $
signe sans produire une erreur de syntaxe. (Je devais ajouter deux espaces plutôt qu'un, car une longueur 180
mettrait un 0
caractère littéral dans la source, ce qui pourrait être abusé dans ce contexte pour comparer zéro avec un mot simple, ce qui aboutit.) Fin de la mise à jour
Une fois que la vérification de la longueur est réussie, nous savons que la copie est intacte, du moins en termes de suppressions de caractères, de sorte que tout est mis en ordre à partir de là (les substitutions de signes de ponctuation dues à une table de décodage corrompue ne seraient pas capturées , mais j’ai déjà vérifié, par la force des brutes, qu’il n’existait pas trois suppressions provenant uniquement de la table de décodage, mais la plupart d’entre elles provoquaient probablement des erreurs de syntaxe. Nous avons $o
dans une variable déjà, donc tout ce que nous devons faire est de mettre en dur (avec des enveloppes à l' extérieur un petit degré de compression, je ne l' ai pas sauter le code golf partie de la question tout à fait ). Une astuce consiste à stocker la majeure partie de la table de codage dans$r
; nous pouvons soit l’imprimer littéralement afin de générer la section de la table de codage de l’emballage interne, soit concaténer du code autour de celle- eval
ci afin de lancer le processus de décodage à l’inverse (nous permettant ainsi de déterminer quelle est la version codée de $ o , seule la version décodée étant disponible à ce stade).
Enfin, si nous étions une copie intacte et pouvions ainsi sortir le programme original dans son intégralité, nous appelons exit
afin d’empêcher les autres copies d’essayer également d’imprimer le programme.
Script de vérification
Pas très joli, mais l'affiche parce que quelqu'un l'a demandé. J'ai couru cela plusieurs fois avec une variété de paramètres (généralement en train de changer $min
et $max
de vérifier différents domaines d'intérêt); ce n'était pas un processus entièrement automatisé. Il a tendance à s’arrêter en raison de la charge de processeur élevée ailleurs; lorsque cela se produisait, je venais juste de passer $min
à la première valeur $x
qui n'était pas entièrement vérifiée et je continuais à exécuter le script (assurant ainsi que tous les programmes de la gamme étaient vérifiés). J'ai seulement vérifié les suppressions de la première copie du programme, car il est assez évident que les suppressions des autres copies ne peuvent pas faire plus.
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
. Je pense que ce serait idéal pour ce genre de défi!