Perl, 1116 1124 bytes, n = 3, puntaje = 1124 ^ (2/3) o aproximadamente 108.1
Actualización : ahora he verificado que esto funciona con n = 3 a través de la fuerza bruta (que tomó un par de días); con un programa tan complejo, es difícil verificar la resistencia a la radiación a mano (y cometí un error en una versión anterior, por lo que aumentó el recuento de bytes). Fin de actualización
Recomiendo redirigir stderr a algún lugar que no lo vea; Este programa produce un montón de advertencias sobre la sintaxis dudosa, incluso cuando no está eliminando caracteres de la misma.
Es posible que el programa se pueda acortar. Trabajar en esto es bastante doloroso, lo que hace que sea fácil pasar por alto posibles micro optimizaciones. Principalmente tenía el objetivo de obtener el mayor número posible de caracteres eliminables (porque esa es la parte realmente desafiante del programa), y traté el desempate del código de golf como algo que era bueno apuntar pero como algo que no pondría esfuerzo ridículo para optimizar (sobre la base de que es muy fácil romper la resistencia a la radiación por accidente).
El programa
Nota: hay un _
carácter de control literal (ASCII 31) inmediatamente antes de cada una de las cuatro ocurrencias de -+
. No creo que se haya copiado y pegado correctamente en StackOverflow, por lo que deberá volver a agregarlo antes de ejecutar el programa.
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 explicación
Este programa está, claramente, compuesto por cuatro programas idénticos más pequeños concatenados juntos. La idea básica es que cada copia del programa verificará si se ha dañado demasiado o no para ejecutarse; si lo ha sido, no hará nada (aparte de posiblemente arrojar advertencias) y dejará que se ejecute la próxima copia; si no lo ha sido (es decir, sin eliminaciones, o el carácter que se eliminó fue uno que no hace ninguna diferencia en la operación del programa), hará su cosa (imprimiendo el código fuente del programa completo; esta es una quine adecuada, con cada parte que contiene una codificación del código fuente completo) y luego salga (evitando que cualquier otra copia no dañada vuelva a imprimir el código fuente y arruinando la quine al imprimir demasiado texto).
Cada parte está hecha a su vez de dos partes que son efectivamente funcionalmente independientes; un contenedor externo y algo de código interno. Como tal, podemos considerarlos por separado.
Envoltura exterior
El envoltorio exterior es, básicamente, eval<+eval<+eval< ... >####>####...>###
(además de un montón de puntos y comas y líneas nuevas cuyo propósito debería ser bastante obvio; es asegurar que las partes del programa permanezcan separadas independientemente de si algunos de los puntos y comas o las nuevas líneas anteriores se eliminan) ) Esto puede parecer bastante simple, pero es sutil de varias maneras, y la razón por la que elegí a Perl para este desafío.
Primero, veamos cómo funciona el contenedor en una copia intacta del programa. eval
analiza como una función incorporada, que toma un argumento. Debido a que se espera una discusión, +
aquí hay un unario +
(que será muy familiar para los golfistas de Perl por ahora; son útiles sorprendentemente a menudo). Todavía estamos esperando un argumento (acabamos de ver un operador unario), por lo <
que lo que viene a continuación se interpreta como el inicio de la<>
operador (que no toma argumentos de prefijo o postfijo, y por lo tanto puede usarse en la posición del operando).
<>
Es un operador bastante extraño. Su propósito habitual es leer los identificadores de archivo y coloca el nombre del identificador de archivo dentro de los corchetes angulares. Alternativamente, si la expresión no es válida como un nombre de identificador de archivo, se bloquea (básicamente, el mismo proceso que usan los shells de UNIX para traducir el texto ingresado por el usuario a una secuencia de argumentos de línea de comandos; en realidad, se usaban versiones mucho más antiguas de Perl) la cáscara para esto, pero hoy en día Perl maneja el globbing interno). El uso previsto, por lo tanto, está en la línea de <*.c>
, que normalmente devolvería una lista como ("foo.c", "bar.c")
. En un contexto escalar (como el argumento paraeval
), solo devuelve la primera entrada que encuentra la primera vez que se ejecuta (el equivalente del primer argumento), y devolvería otras entradas en ejecuciones hipotéticas futuras que nunca suceden.
Ahora, los shells a menudo manejan argumentos de línea de comandos; si da algo como -r
sin argumentos, simplemente se pasará al programa textualmente, independientemente de si hay un archivo con ese nombre o no. Perl actúa de la misma manera, por lo tanto, siempre y cuando nos aseguremos de que no haya caracteres que sean especiales para el shell o para Perl, es casi imposible; Parece que hay dos capas de escape con cada<
y la coincidencia >
, podemos usar esto de manera efectiva como una forma realmente incómoda de literal de cadena. Aún mejor, el analizador de Perl para operadores tipo comillas tiene una tendencia compulsiva a emparejar paréntesis incluso en contextos como este donde no tiene sentido, por lo que podemos anidar de <>
forma segura (que es el descubrimiento necesario para que este programa sea posible). El principal inconveniente de todos estos anidados <>
es que escapar de los contenidos de la<>
<>
, por lo que para escapar de algo en el interior de las tres, debe ir precedido de 63 barras diagonales inversas. Decidí que, aunque el tamaño del código es solo una consideración secundaria en este problema, casi seguro que no valía la pena pagar este tipo de penalización, así que decidí escribir el resto del programa sin usar los caracteres ofensivos.
Entonces, ¿qué sucede si se eliminan partes del contenedor?
- Las eliminaciones en la palabra
eval
hacen que se convierta en una palabra desnuda , una cadena sin significado. A Perl no le gustan estos, pero los trata como si estuvieran rodeados de comillas; asíeal<+eval<+...
se interpreta como"eal" < +eval<+...
. Esto no tiene ningún efecto en la operación del programa, porque básicamente solo toma el resultado de las evaluaciones muy anidadas (que no usamos de todos modos), lo convierte en un número entero y hace algunas comparaciones sin sentido en él. (Este tipo de cosas causa una gran cantidad de spam de advertencia, ya que claramente no es algo útil en circunstancias normales; solo lo estamos usando para absorber eliminaciones). Esto cambia la cantidad de corchetes angulares de cierre que necesitamos (porque el corchete de apertura ahora se está interpretando como un operador de comparación), pero la cadena de comentarios al final asegura que la cadena terminará de manera segura sin importar cuántas veces esté anidada. (Hay más #
signos de los estrictamente necesarios aquí; lo escribí como lo hice para hacer que el programa sea más compresible, permitiéndome usar menos datos para almacenar el quine).
- Si
<
se elimina un, el código ahora se analiza como eval(eval<...>)
. El secundario, externo, eval
no tiene ningún efecto, porque los programas que estamos evaluando no devuelven nada que tenga ningún efecto real como programa (si regresan normalmente, normalmente es una cadena nula o una palabra simple; más comúnmente regresan a través de excepción, que hace eval
que se devuelva una cadena nula, o se usa exit
para evitar el retorno).
- Si
+
se elimina un, esto no tiene efecto inmediato si el código adyacente está intacto; Unary +
no tiene ningún efecto en el programa. (La razón por la que +
están los s originales es para ayudar a reparar el daño; aumentan el número de situaciones en las que <
se interpreta como un operador unario en <>
lugar de como un operador relacional, lo que significa que necesita más eliminaciones para producir un programa no válido).
El contenedor puede dañarse con suficientes eliminaciones, pero debe hacer una serie de eliminaciones para producir algo que no se analice. Con cuatro eliminaciones, puede hacer esto:
eal<evl<eval+<...
y en Perl, el operador relacional <
no es asociativo y, por lo tanto, se obtiene un error de sintaxis (el mismo con el que se obtendría 1<2<3
). Como tal, el límite para el programa tal como está escrito es n = 3. Agregar más +
s unarios parece una forma prometedora de aumentarlo, pero como eso aumentaría la probabilidad de que el interior del contenedor también se rompa, verificando que la nueva versión del programa funcione podría ser muy difícil.
La razón por la que el contenedor es tan valioso es que eval
en Perl detecta excepciones, como (por ejemplo) la excepción que se obtiene cuando intenta compilar un error de sintaxis. Como se eval
trata de un literal de cadena, la compilación de la cadena ocurre en tiempo de ejecución, y si el literal no se compila, la excepción resultante queda atrapada. Esto hace eval
que devuelva una cadena nula y establezca el indicador de error $@
, pero nunca verificamos tampoco (excepto al ejecutar ocasionalmente la cadena nula devuelta en algunas versiones mutadas del programa). Crucialmente, esto significa que si algo le sucede al código dentroel contenedor, causando un error de sintaxis, entonces el contenedor solo hará que el código no haga nada en su lugar (y el programa seguirá ejecutándose en un intento de encontrar una copia no dañada de sí mismo). Por lo tanto, el código interno no tiene que ser tan resistente a la radiación como el envoltorio; todo lo que nos importa es que si está dañado, actuará de forma idéntica a la versión no dañada del programa, o se bloqueará (permitiendo eval
detectar la excepción y continuar) o saldrá normalmente sin imprimir nada.
Dentro de la envoltura
El código dentro del contenedor, fundamentalmente, se ve así (de nuevo, hay un Control, _
que Stack Exchange no mostrará inmediatamente antes del -+
):
eval+(q(...)=~y=A-Z=-+;-AZz-~=r)
Este código está escrito completamente con caracteres seguros para glob, y su propósito es agregar un nuevo alfabeto de signos de puntuación que permitan escribir un programa real, a través de la transcripción y evaluación de un literal de cadena (no podemos usarlo '
o "
como nuestra cita marcas, pero q(
... )
también es una forma válida de formar una cadena en Perl). (La razón del carácter no imprimible es que necesitamos transcribir algo en el carácter de espacio sin un carácter de espacio literal en el programa; por lo tanto, formamos un rango que comienza en ASCII 31 y capturamos el espacio como el segundo elemento del rango). Obviamente, si estamos produciendo algunos caracteres a través de la transliteración, tenemos que sacrificar personajes para transliterarlos de, pero las letras mayúsculas no son muy útiles y es mucho más fácil escribir sin acceso a ellas que sin acceso a signos de puntuación.
Aquí está el alfabeto de los signos de puntuación que están disponibles como resultado del globo (la línea superior muestra la codificación, la línea inferior el carácter que codifica):
BCDEFGHIJKLMNOPQRSTUVWXYZ
! "# $% & '() * +; <=>? @ AZz {|} ~
Lo más notable es que tenemos un montón de signos de puntuación que no son seguros para los globos, pero son útiles para escribir programas de Perl, junto con el carácter de espacio. También guardé dos letras mayúsculas, la literal A
y Z
(que codifican no para sí mismas, sino para T
y U
, porque A
se necesitaba como un punto final de rango superior e inferior); Esto nos permite escribir la instrucción de transliteración en sí usando el nuevo conjunto de caracteres codificados (aunque las letras mayúsculas no son tan útiles, son útiles para especificar cambios en las letras mayúsculas). Los caracteres más notables que no tenemos disponibles son [
, \
y ]
, pero ninguno es necesario (cuando necesitaba una nueva línea en la salida, la produje usando la nueva línea implícita desay
en lugar de necesitar escribir \n
; chr 10
también habría funcionado pero es más detallado).
Como de costumbre, debemos preocuparnos por lo que sucede si el interior del contenedor se daña fuera del literal de la cadena. Un corrupto eval
evitará que algo se ejecute; Estamos bien con eso. Si las comillas se dañan, el interior de la cadena no es válido Perl y, por lo tanto, el contenedor lo atrapará (y las numerosas sustracciones en las cadenas significan que incluso si pudieras hacerlo válido Perl, no haría nada, lo que Es un resultado aceptable). El daño a la transliteración, si no se trata de un error de sintaxis, dañará la cadena que se está evaluando, lo que generalmente hace que se convierta en un error de sintaxis; No estoy 100% seguro de que no haya casos en los que esto se rompa, pero lo estoy forzando por el momento para asegurarme, y debería ser lo suficientemente fácil de solucionar si lo hay.
El programa codificado
Mirando dentro del literal de cadena, invirtiendo la codificación que utilicé y agregando espacios en blanco para que sea más legible, obtenemos esto (nuevamente, imagine un guión bajo de control antes del -+
, que está codificado como 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
Las personas que están acostumbradas a las quines reconocerán esta estructura general. La parte más crucial es al comienzo, donde verificamos que $ o no está dañado; si se han eliminado los personajes, su longitud no coincide 181
, por lo que se corre zzzz((()))
el cual, si no es un error de sintaxis debido a los soportes sin igual, será un error de ejecución, incluso si elimina cualquiera de los tres personajes, porque ninguno de zzzz
, zzz
, zz
, y z
es una función, y no hay forma de evitar que se analice como una función que no sea eliminar (((
y causar un error de sintaxis obvio. El cheque en sí también es inmune al daño; la ||
pueden dañarse a |
pero eso hará que la zzzz((()))
llamada para funcionar sin condiciones; las variables o constantes dañinas causarán una falta de coincidencia porque estás comparando una de 0
,180
,179
, 178
por igualdad a algún subconjunto de los dígitos de 181
; y eliminar uno =
causará una falla de análisis, y dos =
inevitablemente harán que el LHS evalúe el entero 0 o una cadena nula, los cuales son falsey.
Actualización : Esta comprobación fue ligeramente incorrecta en una versión anterior del programa, por lo que tuve que editarla para solucionar el problema. La versión anterior se veía así después de la decodificación:
length$o==179||zzzz((()))
y fue posible eliminar los primeros tres signos de puntuación para obtener esto:
lengtho179||zzz((()))
lengtho179
, siendo una palabra simple, es verdad y, por lo tanto, rompe el control. Lo arreglé agregando dos B
caracteres adicionales (que codifican caracteres de espacio), lo que significa que la última versión de la quine hace esto:
length$o ==181||zzzz((()))
Ahora es imposible ocultar tanto los =
signos como el $
signo sin producir un error de sintaxis. (Tuve que agregar dos espacios en lugar de uno porque una longitud 180
pondría un 0
carácter literal en la fuente, lo que podría ser abusado en este contexto para comparar cero entero con una palabra desnuda, que tiene éxito). Fin de la actualización
Una vez que se pasa la verificación de longitud, sabemos que la copia no está dañada, al menos en términos de eliminaciones de caracteres, por lo que todo es una simple clasificación desde allí (las sustituciones de signos de puntuación debido a una tabla de decodificación corrupta no se detectarán con esta verificación , pero ya he verificado a través de la fuerza bruta que no hay tres eliminaciones de solo la tabla de decodificación que rompen el quine; presumiblemente, la mayoría de ellos causan errores de sintaxis). Ya tenemos $o
una variable, así que todo lo que tenemos que hacer es codificar los envoltorios externos (con un pequeño grado de compresión; no omití la parte del código de golf de la pregunta por completo ). Un truco es que almacenamos la mayor parte de la tabla de codificación en$r
; podemos imprimirlo literalmente para generar la sección de la tabla de codificación del envoltorio interno, o concatenar un código a su alrededor eval
para ejecutar el proceso de decodificación en reversa (lo que nos permite descubrir cuál es la versión codificada de $ o , teniendo solo la versión decodificada disponible en este momento).
Finalmente, si fuéramos una copia intacta y pudiéramos generar el programa original completo, llamamos exit
para evitar que las otras copias también intenten imprimir el programa.
Script de verificación
No es muy bonito, pero lo publica porque alguien preguntó. Ejecuté esto varias veces con una variedad de configuraciones (normalmente cambiando $min
y $max
para verificar varias áreas de interés); No fue un proceso totalmente automatizado. Tiene tendencia a dejar de funcionar debido a la gran carga de CPU en otros lugares; cuando esto sucedió, simplemente cambié $min
al primer valor $x
que no estaba completamente verificado y continué ejecutando el script (asegurando así que todos los programas en el rango se verificaran eventualmente). Solo verifiqué las eliminaciones de la primera copia del programa, porque es bastante obvio que las eliminaciones de las otras copias no pueden hacer más.
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
. ¡Creo que sería ideal para este tipo de desafío!