Programación introspectiva: código que analiza su fuente y su salida.

13

Escriba un programa que genere el número total de caracteres y la frecuencia de cada carácter en su fuente y su salida. Debe seguir el formato ilustrado en el ejemplo.

Ejemplo

Si tu código fuera

abb1

Su salida tendría que ser

My source has 4 characters.
1 is "a"
2 are "b"
1 is "1"
Besides unquoted numbers, my output has 383 characters.
34 are "
"
79 are " "
63 are """
2 are "'"
2 are ","
4 are "."
2 are "1"
2 are "B"
2 are "I"
2 are "M"
39 are "a"
4 are "b"
6 are "c"
4 are "d"
38 are "e"
3 are "g"
5 are "h"
4 are "i"
4 are "m"
3 are "n"
8 are "o"
3 are "p"
2 are "q"
38 are "r"
12 are "s"
8 are "t"
7 are "u"
3 are "y"
It's good to be a program.

(La salida debe ir a stdout).

Observe, por ejemplo, que la salida contiene dos m en mayúscula. Uno para Myy otro para 2 are "M". Esto debe ser cierto para todos los caracteres para que la salida no se contradiga de ninguna manera.

Los números sin comillas se ignoran en la salida para evitar conjuntos de frecuencias insatisfactorios. Por ejemplo, 1 is "1"es incorrecto si se cuentan ambos 1. Debería leer 2 are "1", pero solo hay uno 1 nuevamente.

Aclaraciones de formato

  • "is" debe usarse para ocurrencias de un solo carácter.

  • "are" debe usarse para ocurrencias de múltiples caracteres.

  • "is" nunca debería aparecer en la lista de caracteres de salida porque sería superfluo. 1 is 'Z'se refiere a la Z en sí misma, por lo que se puede eliminar toda la línea.

  • Las tres frases completas deben aparecer en orden con las listas de frecuencia de caracteres entre ellas (como muestra el ejemplo). Entonces su salida comenzará My source...y terminará con ...be a program.. Tenga en cuenta que no hay una nueva línea al final de la salida.

  • Las listas de frecuencia de caracteres pueden estar en cualquier orden.

  • Las nuevas líneas cuentan como un carácter (en caso de que sean \ r \ n).

Comprobador de formato

El siguiente script de Python toma su código y su salida como cadenas y afirma que la salida no tiene contradicciones. Proporciona un mensaje de error útil si algo está mal. Puede ejecutarlo en línea en http://ideone.com/6H0ldu bifurcándolo, reemplazando las cadenas CODE y OUTPUT y luego ejecutándolo. Nunca dará falsos positivos o negativos (suponiendo que esté libre de errores).

#Change the CODE and OUTPUT strings to test your program

CODE = r'''abb1'''

OUTPUT = r'''My source has 4 characters.
1 is "a"
2 are "b"
1 is "1"
Besides unquoted numbers, my output has 383 characters.
34 are "
"
79 are " "
63 are """
2 are "'"
2 are ","
4 are "."
2 are "1"
2 are "B"
2 are "I"
2 are "M"
39 are "a"
4 are "b"
6 are "c"
4 are "d"
38 are "e"
3 are "g"
5 are "h"
4 are "i"
4 are "m"
3 are "n"
8 are "o"
3 are "p"
2 are "q"
38 are "r"
12 are "s"
8 are "t"
7 are "u"
3 are "y"
It's good to be a program.'''

#######################################################

import re

amountPattern = r'(\d+) (is|are) "(.)"\n'

class IntrospectionException(Exception):
    pass

def getClaimedAmounts(string, errorOnIs):
    groups = re.findall(amountPattern, string, re.DOTALL)

    for amount, verb, char in groups:
        if verb == 'is':
            if errorOnIs:
                raise IntrospectionException('\'1 is "%s"\' is unnecessary' % char)
            elif amount != '1':
                raise IntrospectionException('At "%s", %s must use "are"' % (char, amount))
        elif verb == 'are' and amount == '1':
            raise IntrospectionException('At "%s", 1 must use "is"' % char)

    amounts = {}
    for amount, verb, char in groups:
        if char in amounts:
            raise IntrospectionException('Duplicate "%s" found' % char)
        amounts[char] = int(amount)
    return amounts

def getActualAmounts(string):
    amounts = {}
    for char in string:
        if char in amounts:
            amounts[char] += 1
        else:
            amounts[char] = 1
    return amounts

def compareAmounts(claimed, actual):
    for char in actual:
        if char not in claimed:
            raise IntrospectionException('The amounts list is missing "%s"' % char)
    for char in actual: #loop separately so missing character errors are all found first
        if claimed[char] != actual[char]:
            raise IntrospectionException('The amount of "%s" characters is %d, not %d' % (char, actual[char], claimed[char]))
    if claimed != actual:
        raise IntrospectionException('The amounts are somehow incorrect')

def isCorrect(code, output):
    p1 = r'^My source has (\d+) characters\.\n'
    p2 = r'Besides unquoted numbers, my output has (\d+) characters\.\n'
    p3 = r"It's good to be a program\.$"
    p4 = '%s(%s)*%s(%s)*%s' % (p1, amountPattern, p2, amountPattern, p3)

    for p in [p1, p2, p3, p4]:
        if re.search(p, output, re.DOTALL) == None:
            raise IntrospectionException('Did not match the regex "%s"' % p)

    claimedCodeSize = int(re.search(p1, output).groups()[0])
    actualCodeSize = len(code)
    if claimedCodeSize != actualCodeSize:
        raise IntrospectionException('The code length is %d, not %d' % (actualCodeSize, claimedCodeSize))

    filteredOutput = re.sub(r'([^"])\d+([^"])', r'\1\2', output)

    claimedOutputSize = int(re.search(p2, output).groups()[0])
    actualOutputSize = len(filteredOutput)
    if claimedOutputSize != actualOutputSize:
        raise IntrospectionException('The output length (excluding unquoted numbers) is %d, not %d' % (actualOutputSize, claimedOutputSize))

    splitIndex = re.search(p2, output).start()

    claimedCodeAmounts = getClaimedAmounts(output[:splitIndex], False)
    actualCodeAmounts = getActualAmounts(code)
    compareAmounts(claimedCodeAmounts, actualCodeAmounts)

    claimedOutputAmounts = getClaimedAmounts(output[splitIndex:], True)
    actualOutputAmounts = getActualAmounts(filteredOutput)
    compareAmounts(claimedOutputAmounts, actualOutputAmounts)

def checkCorrectness():
    try:
        isCorrect(CODE, OUTPUT)
        print 'Everything is correct!'
    except IntrospectionException as e:
        print 'Failed: %s.' % e

checkCorrectness()

Puntuación

Este es el código de golf. La presentación con la menor cantidad de personajes gana. Las presentaciones deben pasar el verificador de formato para que sea válido. Se aplican las lagunas estándar, aunque puede leer su propio código fuente y / o codificar su salida .

Pasatiempos de Calvin
fuente
¿Está permitido leer su propio archivo fuente?
Ventero
@MrLore Puede haber otros errores, pero me acabo de dar cuenta de que las comillas triples ('' ') aún escapan a las cosas con la barra invertida. Esto puede estar relacionado con su problema. Lo estoy arreglando ahora.
Hobbies de Calvin
@Ventero Definitivamente!
Aficiones de Calvin
@MrLore Las expresiones regulares permiten algunos falsos positivos, sí. Para solucionar el problema con barras invertidas dentro de comillas triples, use cadenas sin formato ( r'''CODE''').
Ventero
1
@MrLore Puntos fijos sin escape. ¡Gracias por notarlo!
Hobbies de Calvin

Respuestas:

2

CJam - 189

{`"_~"+:T;"Besides unquoted numbers, my output has &It's good to be a program.&My source has & characters.
"'&/~_]:X2=T,X3=3i({T_&:B{TI/,(" are ":AM`I*N}fIXK=]o
XBA`N+f+2*+s:T,X3=}fK'q];}_~

Pruébalo en http://cjam.aditsu.net/

Salida:

My source has 189 characters.
3 are "{"
3 are "`"
6 are """
4 are "_"
3 are "~"
4 are "+"
5 are ":"
5 are "T"
2 are ";"
3 are "B"
8 are "e"
9 are "s"
2 are "i"
3 are "d"
17 are " "
6 are "u"
2 are "n"
2 are "q"
8 are "o"
6 are "t"
3 are "m"
2 are "b"
7 are "r"
4 are ","
2 are "y"
2 are "p"
3 are "h"
7 are "a"
5 are "&"
4 are "I"
3 are "'"
2 are "g"
2 are "."
2 are "M"
3 are "c"
2 are "
"
2 are "/"
3 are "]"
5 are "X"
2 are "2"
4 are "="
3 are "3"
2 are "("
2 are "A"
2 are "*"
2 are "N"
3 are "}"
3 are "f"
2 are "K"
Besides unquoted numbers, my output has 988 characters.
3 are "B"
108 are "e"
11 are "s"
3 are "i"
5 are "d"
214 are " "
8 are "u"
4 are "n"
3 are "q"
9 are "o"
9 are "t"
5 are "m"
4 are "b"
108 are "r"
3 are ","
4 are "y"
4 are "p"
6 are "h"
108 are "a"
3 are "I"
3 are "'"
4 are "g"
5 are "."
3 are "M"
7 are "c"
102 are "
"
2 are "{"
198 are """
2 are "`"
2 are "_"
2 are "~"
2 are "+"
2 are ":"
2 are "T"
2 are ";"
2 are "&"
2 are "/"
2 are "]"
2 are "X"
2 are "2"
2 are "="
2 are "3"
2 are "("
2 are "A"
2 are "*"
2 are "N"
2 are "}"
2 are "f"
2 are "K"
It's good to be a program.
aditsu renunció porque SE es MALO
fuente
11

Ruby, 269 (311, 367) caracteres

Tengo tres soluciones diferentes para este desafío. Cada uno de ellos usa un conjunto diferente de trucos:

Solución "adecuada", 367 caracteres:

La solución más larga es más o menos solo una prueba de concepto de que es posible resolver este desafío sin ningún truco, y no está casi totalmente desarrollado. Es una verdadera quine (es decir, genera su propio código fuente en lugar de leerlo de un archivo) y en realidad calcula todos los números que imprime (longitud del código, longitud de salida, ocurrencias de caracteres). Debido a la forma en que funciona el quine, todo el código debe estar en una sola línea y dentro de un literal de cadena.

eval r="S='eval r=%p'%r;O=-~$.;q=\"My source has \#{S.size}\"+(X=' characters.\n')+S.chars.uniq.map{|c|[k=S.count(c),k>O ? :are: :is,?\"+c+?\"]*' '}*$/+'\nBesides unquoted numbers, my output has ';r=(w=q+X+s=\"It's good to be a program.\").scan(D=/\\D/).uniq;$><<q<<(w+v=r.map{|c|j=' are \"\n\"';(-~(w+j*r.size).count(c)).to_s+(j[~O]=c;j)}*$/+$/).scan(D).size<<X+v+s"

Salida parcialmente codificada, 311 caracteres:

La siguiente solución más corta usa dos trucos, pero sigue siendo una verdadera quine: - Ningún carácter aparece exactamente una vez en el código fuente. De esa manera, no necesito decidir si debo imprimir isoare en la primera mitad de la salida. También hace que sea un poco más fácil calcular el tamaño de salida total (aunque en realidad no necesito hacer eso). - El tamaño de salida total está codificado. Como esto solo depende del número de caracteres distintos en el código fuente (y en el caso general, cuántos de esos caracteres aparecen solo una vez), es fácil calcularlo por adelantado.

Tenga en cuenta que el código está precedido por dos líneas nuevas muy importantes, que StackExchange no mostraría en el bloque de código. Por esa razón, he agregado una línea adicional al frente si esas líneas nuevas, que no es parte del código.

#


eval R="I=$/+$/+'eval R=%p'%R;?\\4>w='%d are \"%s\"';B=\"My source has \#{I.size}\#{X=\" characters.\n\"}\#{z=(m=I.chars.uniq).map{|x|w%[I.count(x),x]}*$/}\nBesides unquoted numbers, my output has 1114\"+X;$><<B+m.map{|c|w%[(B+z+$M=\"\nIt's good to be a program.\").gsub!(/\\d++(?!\")/,'').count(c),c]}*$/+$M"

Solución más corta, 269 caracteres:

La solución más corta codifica adicionalmente su propia longitud de fuente. Al usar nombres de variables que son / no son ya parte del código fuente, es posible encontrar un "punto fijo" donde todos los caracteres en el código fuente (¡incluidos los dígitos de las longitudes codificadas!) Aparecen al menos dos veces.

Esta solución también guarda algunos caracteres más simplemente leyendo su propio código fuente del archivo de código, en lugar de generarlo. Como un efecto secundario agradable, esto hace que el código sea mucho más "legible" (pero a quién le importa el código legible en un ...), ya que ahora el código ya no tiene que estar dentro de una cadena literal.

U='%d are "%s"'
O=IO.read$0
?\126>B="My source has 269#{X=" characters.
"}#{z=(m=O.chars.uniq).map{|c|U%[O.count(c),c]}*$/}
Besides unquoted numbers, my output has 1096"+X
$><<B+m.map{|c|U%[(B+z+$M="
It's good to be a program.").gsub!(/\d++(?!")/,"").count(c),c]}*$/+$M

También modifiqué un poco el script de prueba para reducir el pegado de copias necesario para verificar el código. Al reemplazar las definiciones de CODEy OUTPUTcon

import subprocess

CODE = open("packed.rb").read()
OUTPUT = subprocess.check_output(["ruby", "packed.rb"])

print CODE
print len(CODE)

el script ahora ejecuta automáticamente mi código, lee su salida y toma el código fuente del archivo de código.


Aquí está la salida generada por el código más corto:

My source has 269 characters.
3 are "U"
7 are "="
3 are "'"
4 are "%"
6 are "d"
17 are " "
11 are "a"
9 are "r"
9 are "e"
11 are """
11 are "s"
6 are "
"
4 are "O"
2 are "I"
10 are "."
6 are "$"
2 are "0"
2 are "?"
2 are "\"
2 are "1"
2 are "2"
3 are "6"
2 are ">"
4 are "B"
3 are "M"
2 are "y"
9 are "o"
10 are "u"
12 are "c"
4 are "h"
2 are "9"
2 are "#"
4 are "{"
2 are "X"
8 are "t"
4 are "}"
2 are "z"
6 are "("
7 are "m"
5 are "n"
2 are "i"
2 are "q"
6 are ")"
4 are "p"
4 are "|"
2 are "["
4 are ","
2 are "]"
2 are "*"
4 are "/"
3 are "b"
7 are "+"
2 are "<"
3 are "g"
2 are "!"
Besides unquoted numbers, my output has 1096 characters.
2 are "U"
2 are "="
3 are "'"
2 are "%"
5 are "d"
238 are " "
120 are "a"
120 are "r"
120 are "e"
222 are """
11 are "s"
114 are "
"
2 are "O"
3 are "I"
5 are "."
2 are "$"
2 are "0"
2 are "?"
2 are "\"
2 are "1"
2 are "2"
2 are "6"
2 are ">"
3 are "B"
3 are "M"
4 are "y"
9 are "o"
8 are "u"
7 are "c"
6 are "h"
2 are "9"
2 are "#"
2 are "{"
2 are "X"
9 are "t"
2 are "}"
2 are "z"
2 are "("
5 are "m"
4 are "n"
3 are "i"
3 are "q"
2 are ")"
4 are "p"
2 are "|"
2 are "["
3 are ","
2 are "]"
2 are "*"
2 are "/"
4 are "b"
2 are "+"
2 are "<"
4 are "g"
2 are "!"
It's good to be a program.
Ventero
fuente
¿Podría publicar una copia definitiva de su código y salida para que pueda probarlo fácilmente? El código no debe salir en sí mismo y el resultado debe terminar en un período, no en una nueva línea.
Aficiones de Calvin
@ Calvin'sHobbies El primer bloque de código es mi código real. Sin embargo, imprime el resultado con una nueva línea final, así que deme unos minutos para solucionarlo (esto es algo que definitivamente debe mencionar en la especificación).
Ventero
Claro, acabo de actualizar la especificación.
Aficiones de Calvin
@ Calvin'sHobbies Hecho. El primer bloque de código es el código real generado por el segundo bloque de código (para que no tenga que ocuparme del escape de cadenas y todo mientras escribo el código).
Ventero