Programação Introspectiva: Código que analisa sua origem e sua saída


13

Escreva um programa que produza o número total de caracteres e a frequência de cada caractere em sua origem e em sua saída. Você deve seguir o formato ilustrado no exemplo.

Exemplo

Se o seu código foi

abb1

Sua produção teria 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.

(A saída deve ir para stdout.)

Observe, por exemplo, que a saída contém dois m maiúsculos. Um para Mye um para 2 are "M". Isso deve ser verdadeiro para todos os caracteres, para que a saída não se contradiga de forma alguma.

Os números não citados são ignorados na saída para evitar conjuntos de frequências insatisfatórios. Por exemplo, 1 is "1"está incorreto se os 1s forem contados. Deve ler 2 are "1", mas só há um 1 novamente.

Esclarecimentos sobre o formato

  • "is" deve ser usado para ocorrências de caracteres únicos.

  • "are" deve ser usado para várias ocorrências de caracteres.

  • "is" nunca deve aparecer na lista de caracteres de saída porque seria supérfluo. 1 is 'Z'refere-se ao Z em si, para que toda a linha possa ser removida.

  • As três frases completas devem aparecer em ordem com as listas de frequência de caracteres entre elas (como mostra o exemplo). Portanto, sua saída começará com My source...e terminará com ...be a program.. Observe que não há nova linha no final da saída.

  • As listas de frequência de caracteres em si podem estar em qualquer ordem.

  • As novas linhas contam como um caractere (caso sejam \ r \ n).

Verificador de formato

O script Python a seguir usa seu código e sua saída como strings e afirma que a saída não tem contradições. Ele fornece uma mensagem de erro útil se algo estiver errado. Você pode executá-lo on-line em http://ideone.com/6H0ldu , bifurcando-o, substituindo as seqüências de código e OUTPUT e executando-o. Ele nunca fornecerá falsos positivos ou negativos (assumindo que não haja erros).

#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()

Pontuação

Isso é código-golfe. A submissão com o menor número de caracteres vence. Os envios devem passar no verificador de formato para serem válidos. Aplicam-se brechas padrão, embora você possa ler seu próprio código-fonte e / ou codificar sua saída .


É permitido ler seu próprio arquivo de origem?
Ventero

@ MrLore Pode haver outros erros, mas acabei de perceber que as aspas triplas ('' ') ainda escapam às coisas com barra invertida. Isso pode estar relacionado ao seu problema. Estou consertando agora.
Hobbies de Calvin

@Ventero Definitely!
Hobbies de Calvin

@ MrLore Os regexps permitem alguns falsos positivos, sim. Para corrigir o problema com barras invertidas entre aspas triplas, use cadeias brutas ( r'''CODE''').
Ventero

1
@MrLore Corrigidos pontos sem escape. Obrigado por perceber!
Calvin's Hobbies

Respostas:


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];}_~

Experimente em http://cjam.aditsu.net/

Resultado:

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.

11

Ruby, 269 (311, 367) caracteres

Eu tenho três soluções diferentes para esse desafio. Cada um deles usa um conjunto diferente de truques:

Solução "adequada", 367 caracteres:

A solução mais longa é mais ou menos apenas uma prova de conceito de que é possível resolver esse desafio sem truques - e não é quase totalmente disputada. É um verdadeiro quine (isto é, gera seu próprio código-fonte em vez de lê-lo em um arquivo) e, na verdade, calcula todos os números que imprime (comprimento do código, comprimento da saída, ocorrências de caracteres). Devido à maneira como o quine funciona, todo o código deve estar em uma única linha e dentro de uma string literal.

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"

Saída parcialmente codificada, 311 caracteres:

A próxima solução mais curta usa dois truques, mas ainda é uma verdadeira solução: - Nenhum caractere ocorre exatamente uma vez no código-fonte. Dessa forma, não preciso decidir se devo imprimir isou arena primeira metade da saída. Também facilita um pouco o cálculo do tamanho total da saída (embora eu realmente não precise fazer isso). - O tamanho total da saída é codificado. Como isso depende apenas do número de caracteres distintos no código-fonte (e, no caso geral, quantos desses caracteres ocorrem apenas uma vez), é fácil calculá-lo com antecedência.

Observe que o código é precedido por duas novas linhas muito significativas, que o StackExchange não mostraria no bloco de código. Por esse motivo, adicionei uma linha adicional na frente se essas novas linhas, que não fazem parte do 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"

Solução mais curta, 269 caracteres:

A solução mais curta também codifica seu próprio tamanho de fonte. Usando nomes de variáveis ​​que já não fazem parte do código-fonte, é possível encontrar um "ponto de correção" onde todos os caracteres no código-fonte (incluindo os dígitos dos comprimentos codificados!) Ocorrem pelo menos duas vezes.

Essa solução também salva mais alguns caracteres, simplesmente lendo seu próprio código-fonte do arquivo de código, em vez de gerá-lo. Como um bom efeito colateral, isso torna o código muito mais "legível" (mas quem se importa com o código legível em um ...), pois agora o código não precisa mais estar dentro de uma string 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

Também modifiquei um pouco o script de teste para reduzir a cópia-colagem necessária para verificar o código. Substituindo as definições de CODEe OUTPUTcom

import subprocess

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

print CODE
print len(CODE)

o script agora executa automaticamente meu código, lê sua saída e pega o código fonte do arquivo de código.


Aqui está a saída gerada pelo código mais curto:

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.

Você poderia postar uma cópia definitiva do seu código e saída para que eu possa testá-lo facilmente? O código não deve ser emitido por si próprio e deve terminar em um período, não em uma nova linha.
Hobbies de Calvin

@ Calvin'sHobbies O primeiro bloco de código é o meu código real. No entanto, ele imprime a saída com uma nova linha final, portanto, aguarde alguns minutos para corrigir isso (isso é algo que você definitivamente deve mencionar nas especificações).
Ventero

Claro, acabei de atualizar as especificações.
Calvin Hobbies

@ Calvin'sHobbies Feito. O primeiro bloco de código é o código real que é gerado pelo segundo bloco de código (para que eu não precise cuidar da fuga de strings e tudo o mais ao escrever o código).
Ventero
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.