Aqui está um arquivo de texto ASCII de 1.2Mb que contém o texto de Moby-Dick, de Herman Melville ; ou, a baleia . Sua tarefa é escrever um programa ou função (ou classe, etc. - veja abaixo) que receberá esse arquivo com um caractere de cada vez e, a cada passo, deve adivinhar o próximo caractere.
Este é um desafio de código . Sua pontuação será
2*L + E
onde L
é o tamanho do seu envio em bytes e E
o número de caracteres que ele adivinha incorretamente. A pontuação mais baixa vence.
Outras informações
Seu envio será um programa ou função (etc.) que será chamado, invocado ou enviado dados várias vezes. (1215235 vezes para ser exato). Quando ele é chamado para o n º tempo que será dado o n º personagem whale.txt
ou whale2.txt
e deve saída de seu palpite para o ( n + 1 ) th personagem. O E
componente de sua pontuação será o número total de caracteres que ele adivinha incorretamente.
A maioria dos envios precisará armazenar algum estado entre as chamadas, para que eles possam rastrear quantas vezes foram chamados e quais foram as entradas anteriores. Você pode fazer isso gravando em um arquivo externo, usando static
variáveis globais ou, enviando uma classe em vez de uma função, usando uma mônada de estado ou qualquer outra coisa que funcione no seu idioma. Seu envio deve incluir qualquer código necessário para inicializar seu estado antes da primeira chamada.
Seu programa deve executar deterministicamente, para que ele sempre faça as mesmas suposições com a mesma entrada (e, portanto, sempre obtenha a mesma pontuação).
Sua resposta deve incluir não apenas o envio, mas também o código que você usou para calcular a E
parte da pontuação. Isso não precisa ser escrito no mesmo idioma do seu envio e não será contado na contagem de bytes. Você é incentivado a torná-lo legível.
Com relação à interface entre o envio e o programa de cálculo de pontuação, tudo está bem, desde que o programa sempre dê um byte de saída antes de receber o próximo byte de entrada. (Portanto, por exemplo, você não pode simplesmente passar uma string contendo toda a entrada e obter uma string de volta contendo toda a saída.)
Você deve realmente executar seu programa de teste e calcular / verificar sua pontuação antes de enviar sua inscrição. Se o seu envio for muito lento para você verificar sua pontuação, ele não estará qualificado para competir, mesmo que você saiba qual seria sua pontuação em princípio.
O L
componente da sua pontuação será calculado de acordo com as regras usuais para desafios de golfe com código. Se o seu envio contiver vários arquivos, observe as regras de pontuação e estrutura de diretórios nesse caso. Todos os dados que seu código usa devem ser incluídos na sua L
pontuação.
Você pode importar bibliotecas existentes, mas não pode carregar outros arquivos externos, e seu código pode não acessar whale.txt
ouwhale2.txt
arquivo de qualquer maneira que não seja a descrita acima. Você não pode carregar nenhuma rede neural pré-treinada ou outras fontes de dados estatísticos. (Não há problema em usar redes neurais, mas você deve incluir os dados de ponderação em sua submissão e contabilizá-los na contagem de bytes.) Se, por algum motivo, seu idioma ou bibliotecas incluírem um recurso que fornece parte ou todo o texto de Moby Dick , você não pode usar esse recurso. Além disso, você pode usar outros recursos internos ou de biblioteca que desejar, incluindo aqueles relacionados ao processamento, previsão ou compactação de texto, desde que façam parte do seu idioma ou de suas bibliotecas padrão. Para rotinas mais exóticas e especializadas que incluem fontes de dados estatísticos, você teria que implementá-las e incluí-las na sua contagem de bytes.
É provável que alguns envios incluam componentes que são gerados por código. Se for esse o caso, inclua na sua resposta o código que foi usado para produzi-los e explique como ele funciona . (Enquanto esse código não for necessário para executar seu envio, ele não será incluído na sua contagem de bytes.)
Por razões históricas, há duas versões do arquivo e você pode usá-las em uma resposta. Em whale2.txt
(vinculado acima), o texto não é quebrado, portanto as novas linhas aparecem apenas no final dos parágrafos. No original, whale.txt
o texto é dividido em uma largura de 74 caracteres, portanto, você deve prever o final de cada linha e também o texto. Isso torna o desafio mais complicado, por isso whale2.txt
é recomendado para novas respostas. Ambos os arquivos têm o mesmo tamanho, 1215236 bytes.
Para resumir, todas as respostas devem incluir o seguinte:
- Sua submissão em si. (O código, além de todos os arquivos de dados que ele usa - esses podem ser links se forem grandes.)
- Uma explicação de como seu código funciona. Por favor, explique o método de E / S e também como ele prevê o próximo caractere. A explicação do seu algoritmo é importante, e boas explicações receberão recompensas de mim.
- O código que você usou para avaliar sua pontuação. (Se isso é idêntico a uma resposta anterior, você pode simplesmente vincular a ela.)
- Qualquer código que você usou para gerar seu envio, juntamente com uma explicação desse código. Isso inclui o código usado para otimizar parâmetros, gerar arquivos de dados etc. (isso não conta para a contagem de bytes, mas deve ser incluído na sua resposta.)
Entre os melhores
Recompensas
De tempos em tempos, oferecerei recompensas para incentivar diferentes abordagens.
O primeiro, 50 pontos, foi concedido a A. Rex pela resposta de melhor pontuação da época.
O segundo, 100 pontos, também foi concedido a A. Rex, pela mesma resposta, porque eles adicionaram uma explicação muito boa à resposta existente.
A próxima recompensa, 200 pontos , será concedida a qualquer
- Uma resposta competitiva que utiliza uma nova técnica. (Isso será baseado no meu julgamento subjetivo, já que é meu representante que entra na recompensa, mas você pode confiar que eu seja justo. Observe que sua resposta precisa conter uma explicação suficiente para que eu entenda como funciona!) obter a pontuação máxima, ele precisa se sair razoavelmente bem em comparação com as respostas existentes. Estou particularmente interessado em encontrar soluções baseadas em redes neurais recorrentes, mas concederei a recompensa a qualquer coisa que pareça diferente o suficiente dos modelos de Markov que dominam as principais pontuações atuais.
Ou:
- Qualquer pessoa que supere a pontuação máxima de A. Rex (atualmente 444444), usando qualquer método.
Depois que a recompensa de 200 pontos for reivindicada, provavelmente ofereço uma de 400 pontos, atualizando os requisitos de acordo.
fonte
Respostas:
/// , 2 * 1 + 1020874 = 1020876
Imprime um espaço.
fonte
Node.js, 2 * 224 + 524279 = 524727
Consulte o log de alterações no final deste post para atualizações de pontuação.
Uma função que recebe e retorna um byte.
Consiste em um modelo PPM simples que analisa os últimos 8 caracteres para prever o próximo.
Confiamos em um padrão de comprimento L quando o encontramos pelo menos T [L] vezes, em que T é uma matriz de limites arbitrários: [1,1,2,1,2,3,5,2] . Além disso, sempre confiamos em um padrão cujo primeiro caractere corresponde
[A-Z '"(]
.Selecionamos o padrão confiável mais longo e retornamos a previsão com a pontuação mais alta associada a esse padrão no momento da chamada.
Notas
Obviamente, isso não é otimizado para velocidade, mas é executado em cerca de 15 segundos no meu laptop.
Se pudéssemos repetir o processo várias vezes seguidas sem redefinir o modelo, o número de erros convergiria para ~ 268000 após 5 iterações.
A taxa de sucesso atual da função de previsão é de ~ 56,8%. Conforme observado por @immibis nos comentários, se palpites ruins e corretos são misturados, o resultado nem é quase legível.
Por exemplo, este trecho próximo ao final do livro:
torna-se:
Ao substituir palpites ruins por sublinhados, temos uma idéia melhor do que a função deu certo:
Nota : O exemplo acima foi criado com uma versão anterior do código, trabalhando na primeira versão do arquivo de entrada.
Código de teste
Log de alterações
fonte
sidg tlanses,oeth to, shuld hottut tild aoersors Ch, th! Sa, yr! Sheu arinning whales aut ihe e sl he traaty of rrsf tg homn Bho dla tiasot a shab sor ty, af etoors tnd hocket sh bts ait mtubb tiddin tis aeewnrs, dnhost maundy cnd sner aiwt d boelh cheugh -aaieiyns aasiyns taaeiins! th, tla
. Ele consegue obter algumas palavras completas às vezes. Likewhales
.Perl, 2 · 70525 + 326508 = 467558
Preditor
Para executar este programa, você precisa deste arquivo aqui , que deve ser nomeado
B
. (Você pode alterar esse nome de arquivo na segunda instância do caractereB
acima.) Veja abaixo como gerar esse arquivo.O programa usa uma combinação de modelos de Markov essencialmente como nesta resposta do usuário 2699 , mas com algumas pequenas modificações. Isso produz uma distribuição para o próximo caractere. Usamos a teoria da informação para decidir se devemos aceitar um erro ou gastar bits de armazenamento em
B
dicas de codificação (e, se sim, como). Utilizamos a codificação aritmética para otimizar os bits fracionários do modelo.O programa tem 582 bytes (incluindo uma nova linha final desnecessária) e o arquivo binário
B
tem 69942 bytes; portanto, de acordo com as regras de pontuação de vários arquivos , obtemosL
582 + 69942 + 1 = 70525.O programa quase certamente requer uma arquitetura de 64 bits (little-endian?). Demora aproximadamente 2,5 minutos para executar em uma
m5.large
instância no Amazon EC2.Código de teste
O equipamento de teste assume que o envio está no arquivo
submission.pl
, mas isso pode ser facilmente alterado na segunda linha.Comparação de texto
Este exemplo (escolhido em outra resposta ) ocorre bastante tarde no texto, portanto o modelo é bastante desenvolvido nesse ponto. Lembre-se de que o modelo é aumentado por 70 kilobytes de "dicas" que ajudam diretamente a adivinhar os personagens; não é direcionado simplesmente pelo pequeno trecho de código acima.
Gerando dicas
O programa a seguir aceita o código exato de envio acima (na entrada padrão) e gera o
B
arquivo exato acima (na saída padrão):Demora aproximadamente o tempo necessário para executar o envio, pois ele executa cálculos semelhantes.
Explicação
Nesta seção, tentaremos descrever o que essa solução faz com detalhes suficientes para que você possa "experimentá-la em casa". A principal técnica que diferencia essa resposta das outras é algumas seções abaixo, como o mecanismo de "rebobinar", mas antes de chegarmos lá, precisamos configurar o básico.
Modelo
O ingrediente básico da solução é um modelo de linguagem. Para nossos propósitos, um modelo é algo que pega uma certa quantidade de texto em inglês e retorna uma distribuição de probabilidade no próximo caractere. Quando usamos o modelo, o texto em inglês será um prefixo (correto) de Moby Dick. Observe que a saída desejada é uma distribuição e não apenas um palpite para o caracter mais provável.
No nosso caso, usamos essencialmente o modelo nesta resposta do usuário2699 . Não usamos o modelo da resposta de maior pontuação (exceto a nossa) de Anders Kaseorg justamente porque não conseguimos extrair uma distribuição em vez de apenas um palpite. Em teoria, essa resposta calcula uma média geométrica ponderada, mas obtivemos resultados um pouco ruins quando a interpretamos literalmente. "Roubamos" um modelo de outra resposta, porque nosso "molho secreto" não é o modelo, mas a abordagem geral. Se alguém tiver um modelo "melhor", deverá conseguir melhores resultados usando o restante de nossas técnicas.
Como observação, a maioria dos métodos de compactação, como o Lempel-Ziv, pode ser vista como um "modelo de linguagem" dessa maneira, embora seja necessário apertar os olhos um pouco. (É particularmente complicado para algo que transforma uma Burrows-Wheeler!) Além disso, observe que o modelo por user2699 é uma modificação de um modelo de Markov; essencialmente, nada mais é competitivo para esse desafio ou talvez até modelar texto em geral.
Arquitetura geral
Para fins de entendimento, é bom dividir a arquitetura geral em várias partes. Da perspectiva de mais alto nível, é necessário haver um pouco de código de gerenciamento de estado. Isso não é particularmente interessante, mas, para ser completo, queremos enfatizar que, em todo momento, o programa é solicitado a adivinhar o seguinte, ele tem disponível um prefixo correto de Moby Dick. Nós não usamos nossas suposições incorretas do passado de forma alguma. Por uma questão de eficiência, o modelo de linguagem provavelmente pode reutilizar seu estado dos primeiros N caracteres para calcular seu estado para os primeiros (N + 1) caracteres, mas, em princípio, ele pode recompilar as coisas do zero toda vez que é invocado.
Vamos deixar esse "driver" básico de lado e espiar dentro da parte que adivinha o próximo caractere. Conceitualmente, ajuda a separar três partes: o modelo de linguagem (discutido acima), um arquivo de "dicas" e um "intérprete". A cada passo, o intérprete solicitará ao modelo de linguagem uma distribuição para o próximo caractere e possivelmente lerá algumas informações do arquivo de dicas. Em seguida, combinará essas partes em um palpite. Precisamente, quais informações estão no arquivo de dicas e como são usadas serão explicadas mais tarde, mas, por enquanto, ajuda a manter essas partes separadas mentalmente. Observe que, em termos de implementação, o arquivo de dicas é literalmente um arquivo (binário) separado, mas poderia ter sido uma string ou algo armazenado dentro do programa. Como uma aproximação,
Se alguém estiver usando um método de compactação padrão, como bzip2, como nesta resposta , o arquivo "dicas" corresponderá ao arquivo compactado. O "intérprete" corresponde ao descompressor, enquanto o "modelo de linguagem" é um pouco implícito (como mencionado acima).
Por que usar um arquivo de dica?
Vamos escolher um exemplo simples para analisar melhor. Suponha que o texto tenha
N
caracteres longos e bem aproximados por um modelo em que cada caractere seja (independentemente) a letraE
com probabilidade ligeiramente menor que a metade,T
similarmente com probabilidade ligeiramente menor que a metade eA
com probabilidade 1/1000 = 0,1%. Vamos supor que nenhum outro personagem seja possível; de qualquer forma,A
é bem parecido com o de um personagem anteriormente invisível do nada.Se operamos no regime L0 (como a maioria, mas não todas, das outras respostas a essa pergunta), não há melhor estratégia para o intérprete do que escolher uma das opções
E
eT
. Em média, ele receberá cerca da metade dos caracteres corretos. Então E ≈ N / 2 e a pontuação ≈ N / 2 também. No entanto, se usarmos uma estratégia de compactação, podemos compactar para um pouco mais de um bit por caractere. Como L é contado em bytes, obtemos L ≈ N / 8 e, portanto, obtemos ≈ N / 4, duas vezes melhor que a estratégia anterior.Atingir essa taxa de pouco mais de um bit por caractere para este modelo não é trivial, mas um método é a codificação aritmética.
Codificação aritmética
Como é comumente conhecido, uma codificação é uma maneira de representar alguns dados usando bits / bytes. Por exemplo, ASCII é uma codificação de 7 bits / caracteres do texto em inglês e caracteres relacionados, e é a codificação do arquivo Moby Dick original em consideração. Se algumas letras são mais comuns que outras, uma codificação de largura fixa como ASCII não é ideal. Em tal situação, muitas pessoas buscam a codificação de Huffman . Isso é ideal se você deseja um código fixo (sem prefixo) com um número inteiro de bits por caractere.
No entanto, a codificação aritmética é ainda melhor. Grosso modo, é capaz de usar bits "fracionários" para codificar informações. Existem muitos guias para codificação aritmética disponíveis online. Iremos pular os detalhes aqui (especialmente a implementação prática, que pode ser um pouco complicada do ponto de vista da programação) por causa dos outros recursos disponíveis online, mas se alguém reclamar, talvez esta seção possa ser mais aprofundada.
Se houver texto realmente gerado por um modelo de linguagem conhecido, a codificação aritmética fornecerá uma codificação essencialmente ótima do texto desse modelo. Em certo sentido, isso "resolve" o problema de compactação desse modelo. (Portanto, na prática, a questão principal é que o modelo não é conhecido e alguns são melhores que outros na modelagem de texto humano.) Se não foi permitido cometer erros neste concurso, então no idioma da seção anterior , uma maneira de produzir uma solução para esse desafio seria usar um codificador aritmético para gerar um arquivo "dicas" a partir do modelo de linguagem e, em seguida, usar um decodificador aritmético como "intérprete".
Nesta codificação essencialmente ideal, acabamos gastando -log_2 (p) bits para um caractere com probabilidade p, e a taxa de bits geral da codificação é a entropia de Shannon . Isso significa que um caractere com probabilidade próxima a 1/2 leva cerca de um bit para codificar, enquanto um caractere com probabilidade 1/1000 leva cerca de 10 bits (porque 2 ^ 10 é aproximadamente 1000).
Mas a métrica de pontuação para esse desafio foi bem escolhida para evitar a compactação como a estratégia ideal. Teremos que descobrir uma maneira de cometer alguns erros como compensação por obter um arquivo de dicas mais curto. Por exemplo, uma estratégia que se pode tentar é uma estratégia simples de ramificação: geralmente tentamos usar a codificação aritmética quando possível, mas se a distribuição de probabilidade do modelo é "ruim", de alguma forma, apenas adivinhamos o personagem mais provável e não ' tente codificá-lo.
Por que cometer erros?
Vamos analisar o exemplo anterior para motivar por que podemos querer cometer erros "intencionalmente". Se usarmos a codificação aritmética para codificar o caractere correto, gastaremos aproximadamente um bit no caso de um
E
ouT
, mas cerca de dez bits no caso de umA
.No geral, essa é uma codificação muito boa, gastando um pouco mais de um pouco por caractere, embora haja três possibilidades; basicamente,
A
é bastante improvável e não acabamos gastando seus dez bits correspondentes com muita frequência. No entanto, não seria bom se pudéssemos cometer um erro no caso de umA
? Afinal, a métrica para o problema considera 1 byte = 8 bits de comprimento como equivalente a 2 erros; portanto, parece que se deve preferir um erro ao invés de gastar mais de 8/2 = 4 bits em um caractere. Gastar mais de um byte para salvar um erro definitivamente parece subótimo!O mecanismo de "rebobinar"
Esta seção descreve o principal aspecto inteligente desta solução, que é uma maneira de lidar com suposições incorretas sem nenhum custo.
Para o exemplo simples que analisamos, o mecanismo de rebobinagem é particularmente direto. O intérprete lê um bit do arquivo de dicas. Se é um 0, adivinha
E
. Se for um 1, adivinhaT
. Na próxima vez que for chamado, ele verá qual é o caractere correto. Se o arquivo de dica estiver bem configurado, podemos garantir que, no caso de umE
ouT
, o intérprete adivinhe corretamente. Mas que talA
? A ideia do mecanismo de retrocesso é simplesmente não codificarA
em tudo . Mais precisamente, se o intérprete mais tarde descobrir que o caractere correto era umA
, metaforicamente " rebobina a fita": retorna o bit lido anteriormente. A parte que ele lê pretende codificarE
ouT
, mas agora não; será usado mais tarde. Neste exemplo simples, isso basicamente significa que continua adivinhando o mesmo caractere (E
ouT
) até acertar; depois lê outro pedaço e continua.A codificação para esse arquivo de dicas é muito simples: transforme todos os
E
s em 0 bitsT
es em 1 bits, ignorando-osA
completamente. Pela análise no final da seção anterior, esse esquema comete alguns erros, mas reduz a pontuação geral ao não codificar nenhum dosA
s. Como efeito menor, ele também economiza o tamanho do arquivo de dicas, porque acabamos usando exatamente um bit para cada umE
eT
, em vez de um pouco mais do que um pouco.Um pouco de teorema
Como decidimos quando cometer um erro? Suponha que nosso modelo nos dê uma distribuição de probabilidade P para o próximo caractere. Separaremos os caracteres possíveis em duas classes: codificado e não codificado . Se o caractere correto não estiver codificado, usaremos o mecanismo "rebobinar" para aceitar um erro sem nenhum custo. Se o caractere correto for codificado, usaremos outra distribuição Q para codificá-lo usando codificação aritmética.
Mas qual distribuição Q devemos escolher? Não é difícil ver que todos os caracteres codificados devem ter maior probabilidade (em P) do que os caracteres não codificados. Além disso, a distribuição Q deve incluir apenas os caracteres codificados; afinal, não estamos codificando os outros, por isso não devemos "gastar" entropia neles. É um pouco mais complicado ver que a distribuição de probabilidade Q deve ser proporcional a P nos caracteres codificados. Reunir essas observações significa que devemos codificar os caracteres mais prováveis, mas possivelmente não os menos prováveis, e que Q é simplesmente P redimensionado nos caracteres codificados.
Além disso, verifica-se que existe um teorema interessante sobre qual "corte" deve-se escolher para os caracteres de codificação: você deve codificar um caractere contanto que seja pelo menos 1 / 5.393 tão provável quanto os outros caracteres codificados combinados. Isso "explica" a aparência da constante aparentemente aleatória
5.393
mais próxima do final do programa acima. O número 1 / 5.393 ≈ 0,18542 é a solução para a equação -p log (16) - p log p + (1 + p) log (1 + p) = 0 .Talvez seja uma idéia razoável escrever esse procedimento no código. Este fragmento está em C ++:
Juntando tudo
Infelizmente, a seção anterior é um pouco técnica, mas se juntarmos todas as outras peças, a estrutura será a seguinte. Sempre que o programa é solicitado a prever o próximo caractere após um determinado caractere correto:
A codificação do arquivo de dicas funciona da mesma forma. Nesse caso, o programa sabe qual é o próximo caractere correto. Se é um caractere que deve ser codificado, é claro que se deve usar o codificador aritmético; mas se for um caractere não codificado, ele simplesmente não atualizará o estado do codificador aritmético.
Se você entende o contexto teórico da informação como distribuições de probabilidade, entropia, compressão e codificação aritmética, mas tentou e não conseguiu entender este post (exceto por que o teorema é verdadeiro), informe-nos e podemos tentar esclarecer as coisas. Obrigado pela leitura!
fonte
B
arquivo? Em caso afirmativo, você pode incluir isso em sua resposta?Python 3, 2 · 267 + 510193 = 510727
Preditor
Utiliza uma combinação bayesiana ponderada dos modelos Markov da ordem 0,…, 16, com pesos [1, 6, 12, 30, 65, 99, 87, 117, 118, 89, 95, 118, 96, 184, 126, 219, 126].
O resultado não é muito sensível à seleção desses pesos, mas eu os otimizei porque pude, usando o mesmo algoritmo de escalada de aceitação tardia que usei na minha resposta para “Reunir a maioria do Senado” , onde cada mutação candidata é apenas um incremento de ± 1 para um único peso.
Código de teste
fonte
b"\0\3\6\r\34'&-20'\22!P\n[\26"
é a representação ascii dos pesos, onde pequenos valores não imprimíveis são escapados em octal.Python 3 ,
2 * 279 + 592920 = 5934782 * 250 + 592467 = 5929672 * 271 + 592084 = 5926262 * 278 + 592059 = 5926152 * 285 + 586660 = 5872302 * 320 + 585161 = 5858012 * 339 + 585050 = 585728Experimente online!
Uma função usando variáveis globais. Aprende à medida que constrói um modelo no nível da palavra: dado o que é visto até agora nesta palavra , qual é o próximo personagem mais comum? À medida que mais informações entram, ele aprende muito bem as palavras comuns do texto e também aprende o caractere mais comum para iniciar a próxima palavra.
Por exemplo:
Não se sai muito bem no início, mas no final existem grandes partes de palavras reais saindo. A opção de fallback é um espaço e, após um único espaço, é um "a", a menos que a letra anterior seja de "nedtfo", um dígito ou um hífen ou apóstrofo. Ele também prevê agressivamente quebras de linha após 71 caracteres ou se um espaço era esperado após 66. Ambos foram sintonizados com os dados ("t" é muito mais comum após um espaço, mas já foi previsto com mais frequência, portanto " um "é um palpite melhor fora desses seis casos especiais).
Aprender quais pares de palavras foram combinados e prever o mapeamento acabou não valendo a pena.
Termina com um texto como este:
que corresponde a esta parte da entrada:
Você pode ver onde os nomes próprios em particular saem muito bem, mas o final das palavras também está correto. Quando é visto "dou", espera "dúvida", mas quando o "l" aparece, ele fica "dobrado".
Se você executá-lo pela segunda vez com o mesmo modelo que ele acabou de criar, obtém imediatamente outros 92k de correção (51,7% -> 59,3%), mas sempre fica abaixo de 60% a partir da segunda iteração.
O código de medição está no link TIO, ou aqui está uma versão um pouco melhor:
guess.txt
tem a saída estimada no final.fonte
C ++, pontuação: 2 * 132 + 865821 = 866085
Obrigado a @Quentin por salvar 217 bytes!
Uma solução muito simples que, dada um caractere, apenas gera o caractere que aparece com mais frequência após o caractere de entrada.
Verifique a pontuação com:
Editar: Usar
whale2.txt
fornece uma pontuação melhor.fonte
L
salvar um monte de personagens :)Python, 2 * 516 + 521122 = 522154
Algoritmo:
Ainda outra submissão em python, esse algoritmo calcula a próxima letra mais provável, observando seqüências de comprimento 1, ..., l. A soma das probabilidades é usada e existem alguns truques para obter melhores resultados.
Resultados:
Principalmente sem sentido, embora você possa ver que ele pega uma frase ocasional, como "Pai Mapple".
Código do teste:
Muito simples, gera alguns exemplos do texto em diferentes pontos. Usa whale2.txt, pois isso evita uma lógica extra para calcular novas linhas.
fonte
C (gcc) ,
6797876528928476 bytes,679619652740 suposições incorretasExperimente online!
Atualização: ~ 27000 pontos com o arquivo atualizado, 16 pontos (8 bytes) com uma função com melhor desempenho.
Explicação
A maneira como isso funciona é que, à medida que o código percorre o texto, ele memoriza o último caractere que encerrou qualquer sequência de 4 caracteres e retorna esse valor. Um pouco semelhante à abordagem de Arnauld acima, mas se baseia na probabilidade inerente de duas seqüências de 4 caracteres que terminam da mesma maneira.
De-golfe:
fonte
sh + bzip2, 2 * 364106 = 728212
2 * 381249 + 0 = 762498seguido pelo bzip2-compressed whale2.txt com o primeiro byte ausente
Ignora sua entrada; gera a resposta correta. Isso fornece uma linha de base em uma extremidade; daniero fornece uma linha de base na outra extremidade.
Script do construtor:
Arnês de teste de E / S (tcc; corte da primeira linha para gcc). Esse equipamento de teste pode ser usado por qualquer pessoa em uma plataforma adequada que envie um programa completo que espere E / S de leitura / gravação. Ele usa E / S de byte por vez para evitar trapaças. O programa filho deve liberar a saída após cada byte para evitar o bloqueio.
fonte
but may not load any other external files, and your code may not access the whale.txt file in any way other than described above.
cláusula?nth
tempo, receberá o enésimo caractere dewhale.txt
ouwhale2.txt
e deve apresentar seu palpite para o(n+1)th
personagem." - Como esse requisito é cumprido? O código exibe o texto inteiro dewhale.txt
cada vez que é executado.Python 3 , 879766
Experimente online!
... A
///
resposta que imprime um espaço recebe 10 votos positivos, enquanto meu código só pode receber 3 ...Explicação:
Para cada personagem, o programa:
frequency[prev][char]
frequency[char]
que têm a pontuação total
Como não há como fazer upload de um arquivo grande para o TIO (exceto pedir a Dennis), o exemplo executado no link TIO executa o programa apenas para uma pequena parte do texto.
Em comparação com a resposta mais antiga, essa possui 362 caracteres mais incorretos, mas o código é mais curto em 255 bytes. O multiplicador faz com que minha inscrição tenha uma pontuação menor.
fonte
C #, 378 * 2 + 569279 = 570035
Essa abordagem usa uma tabela de consulta para aprender o caractere mais comum que segue uma determinada sequência. As teclas da tabela de pesquisa têm no máximo 4 caracteres; portanto, a função atualiza primeiro a tabela de pesquisa com o caractere atual e depois apenas verifica qual caractere é o mais provável de ocorrer após os 4 anteriores, incluindo o atual . Se esses 4 caracteres não forem encontrados na tabela de pesquisa, ele imprimirá um espaço.
Esta versão usa o
whale2.txt
arquivo, pois melhora bastante o número de suposições bem-sucedidas.A seguir está o código usado para testar a classe:
O código é executado em apenas 2 segundos. Apenas para constar, é isso que recebo quando modifico o tamanho das chaves da tabela de consulta (inclui os resultados de uma segunda execução sem redefinir o modelo):
Seria interessante saber por que um tamanho de chave de 4 caracteres é a melhor escolha nesse algoritmo.
Comparação de texto
Original:
Recriado:
Palpites:
Log de alterações
whale2.txt
e, portanto, removido a otimização.fonte
Java 7, caracteres de 1995, (1995 * 2 + 525158) 529148
Java é péssimo para pequenos tamanhos de programa. Enfim, tentei várias abordagens extremamente complexas e complicadas que produziram resultados surpreendentemente ruins. Posteriormente, voltei e fiz uma abordagem simples, que resultou em um tamanho menor de programa e melhores resultados.
Essa abordagem é realmente extremamente simples. Alimenta cegamente os x caracteres anteriores (além de todas as substrings desses caracteres) em uma hashtable, mapeada para o caractere atual. Em seguida, ele acompanha quais padrões preveem com mais precisão o caractere atual. Se padrões que precedem certos caracteres forem encontrados várias vezes, eles conseguirão prever o personagem. Dá precedência a seqüências mais longas e dá precedência a qualquer caractere que mais frequentemente segue uma determinada sequência. Esse algoritmo não sabe nada sobre o tipo de documento ou o idioma inglês.
Decidi usar 9 caracteres e tentar combinar palavras inteiras nos 9 caracteres anteriores, quando possível. Quando você não tenta fazer a correspondência de palavras nas cadeias, o tamanho ideal é de 6 caracteres, produzindo milhares de outras previsões erradas.
Uma observação interessante foi que o uso de 20 caracteres resultou em previsões ruins na primeira vez, mas com precisão de 99,9% nas passagens subsequentes. O algoritmo foi basicamente capaz de memorizar o livro em pedaços de 20 bytes sobrepostos, e isso foi distinto o suficiente para permitir que ele lembrasse o livro inteiro de um caractere por vez.
fonte
Python 3,
2 × 497 + 619608 = 6206022 × 496 + 619608 = 620600Tentei isso de forma independente, mas acabei com o que é efetivamente uma versão inferior da resposta de Michael Homer. Espero que isso não torne minha resposta completamente obsoleta.
Isso cria ao longo do tempo um dicionário de palavras (definido de forma grosseira como cadeias terminadas por
ou
\n
, diferenciando maiúsculas de minúsculas e incluindo pontuação). Em seguida, ele pesquisa neste dicionário por palavras que começam com o que até agora conhece a palavra atual, classifica a lista resultante por frequência de ocorrência (lentamente) e supõe que o próximo caractere seja o próximo na palavra correspondente mais comum. Se já tivermos a palavra correspondente mais comum ou se não existir mais, ela retornará.
Ele também cria um dicionário repugnantemente ineficiente de pares de palavras. Ao atingir um limite de palavra, ele adivinha que o próximo caractere é a primeira letra da segunda palavra no par de palavras correspondente mais comum ou
t
se não há correspondência. Não é muito inteligente, no entanto. A seguirMoby
, o programa adivinha corretamente que o próximo personagem éD
, mas depois esquece tudo sobre o contexto e geralmente acaba chamando a baleia de "Moby Duck" (porque a palavra "holandês" parece ser mais frequente na primeira metade do texto ) Seria fácil corrigir isso priorizando pares de palavras em detrimento de palavras individuais, mas espero que o ganho seja marginal (já que geralmente é correto a partir do terceiro caractere, e os pares de palavras não são tão úteis em primeiro lugar).Eu poderia ajustar isso para corresponder melhor ao texto fornecido, mas não acho que o ajuste manual do algoritmo com base no conhecimento prévio da entrada esteja realmente no espírito do jogo, exceto por escolher t como o caractere de fallback após um espaço ( e eu provavelmente não deveria ter feito isso também), evitei isso. Eu ignorei o comprimento da linha conhecida do arquivo de entrada e, em vez disso, inseri-lo
\n
após cada 13 espaços - é quase certamente uma correspondência muito ruim, a principal intenção era manter o comprimento da linha razoável, em vez de corresponder à entrada.O código não é exatamente rápido (~ 2 horas na minha máquina), mas no geral obtém cerca da metade dos caracteres corretos (49%). Espero que a pontuação seja marginalmente melhor se for executada
whale2.txt
, mas ainda não o fiz.O início da saída é assim:
mas no final, parece um pouco mais com ... alguma coisa. Minha passagem favorita do final do livro,
sai como
Isso teria tornado A Ira de Khan muito mais confusa. E "solitário" → "formigamento" é uma substituição particularmente satisfatória.
Editar: salvou um byte excluindo um espaço estranho
Pontuação
Isso executa o programa para o texto de Moby Dick e gera o texto "previsto" para stdout e abusa do stderr para escrever a pontuação. Eu recomendo redirecionar a saída para um arquivo.
fonte
lambda i:i[1]
seria mais barato do que lidaroperator
?C ++, 2 · 62829 + 318786 = 444444
Para executar este programa, você precisa deste arquivo aqui , que deve ser nomeado
C
.O programa usa a mesma combinação de modelos de Markov que em nossa resposta anterior . Como antes, essa combinação é essencialmente o modelo desta resposta do usuário2699 , mas com algumas pequenas modificações.
Visto que essa resposta usa exatamente o mesmo modelo de antes, a melhoria é um mecanismo teórico da informação melhor do que o mecanismo de "rebobinar" descrito anteriormente. Isso permite cometer menos erros e também ter um comprimento combinado menor. O programa em si não é muito jogado, porque não é o principal colaborador da pontuação.
O programa tem 2167 bytes de comprimento (incluindo todas as guias para indentação e muitos outros caracteres desnecessários, mas antes do código de teste) e o arquivo binário
C
tem 60661 bytes, portanto, de acordo com as regras de pontuação de vários arquivos , obtemosL
2167 + 60661 + 1 = 62829.O programa leva aproximadamente 8 minutos para ser executado em uma
m5.4xlarge
instância no Amazon EC2 e usa um pouco mais de 16 GB de memória. (Esse uso excessivo de memória não é necessário - nós também não otimizamos isso.)fonte
Python 3, 526640
274 bytes, erros 526092 (usando
whale2.txt
). Definitivamente, isso é capaz de melhorar ainda mais, mas chegou ao estágio "bom o suficiente para publicar".A idéia é armazenar as frequências de todas as execuções de 2, 3, 4, ..., 10 caracteres. Para cada um desses comprimentos L, verificamos se os caracteres L-1 mais recentes correspondem a um padrão armazenado; Nesse caso, nosso palpite g L é o próximo caractere mais frequente após esse padrão. Coletamos até nove palpites dessa maneira. Para decidir qual palpite usar, ponderamos a frequência de cada padrão pelo seu comprimento até a 8ª potência. O palpite com a maior soma de frequências ponderadas é escolhido. Se não houver padrões que correspondam, adivinhamos espaço.
(O comprimento máximo do padrão e o expoente de ponderação foram escolhidos por tentativa e erro para fornecer o menor número possível de suposições incorretas.)
Aqui está minha versão de trabalho em andamento não destruída:
E o chicote de teste:
Aqui está um exemplo de saída do início do texto. Já começamos a ver a capacidade de terminar palavras depois de ver sua primeira carta (
in
,to
,and
,by
, também, aparentemente,school
).Perto do final, ainda existem muitos erros, mas também muitas seqüências muito boas (
shmage seashawks
por exemplo).É interessante observar alguns dos erros e adivinhar qual palavra o algoritmo "esperava". Por exemplo, depois
sail
, o programa ambas as vezes predizo
--parasailor
, presumo. Ou então, depois que, a
ele espera -n
possivelmente por causa da ocorrência comum de, and
.Changelog:
fonte
Python 2, pontuação: 2 * (407 + 56574) + 562262 = 676224
Procura por palavras que correspondam aos caracteres anteriores em uma lista de
todas aspalavras mais usadas no texto, classificadas pelo número de ocorrências.Código:
Dados: https://www.dropbox.com/s/etmzi6i26lso8xj/d?dl=0
Suíte de teste:
Editar: Usar
whale2.txt
fornece uma pontuação melhor.fonte
C ++ (GCC), 725 × 2 + 527076 = 528526
Mais um envio de prefixo-frequência. Corra
whale2.txt
e obtenha pontuação semelhante (um pouco pior) do que outras.Este avidamente encontra a corda mais longa que começa com um sufixo da história e, se houver vários candidatos, faça o tiebreak com cordas mais curtas.
Por exemplo: Se os últimos 7 personagens são
abcdefgh
, ea cadeiaabcdefghi
eabcdefghj
aparece com a maior frequência em todas as cordas da formaabcdefgh*
, a saída será tantoi
ouj
, tiebreak com sufixos mais curtos (bcdefgh
,cdefgh
...).Por razões desconhecidas, nada mais que 7 e meu computador não tem RAM suficiente para executá-lo. Mesmo com 7, preciso fechar todos os navegadores da web para executá-lo.
Código de teste:
Ungolfed:
Exemplo de saída:
Este está perto do final do texto. A maioria das palavras longas são previstos bastante precisão (
intervals
,pivot-hole
,distance
)Letras maiúsculas não parecem boas.
fonte
Python 2, 756837
Usa algo que pode ser cadeias de Markov?
fonte
zlib.decompress('...')
Avalia{'G?':' ', 'G;':' ','G"':' ',.......}
ea
é um dicionário que mapeia de 2 para 1 caractere. Basicamente, variante de 2 caracteres da resposta do Steadybox .xxd
,hexdump
,uuencode
, Ou similarHaskell, (1904 + 1621 + 208548 + 25646) * 2 + 371705 = 847143
Exemplo:
Usa três arquivos auxiliares pré-calculados:
seqwords
contém as 236 palavras mais comuns.wordsseq
contém uma sequência comprimida por LZMA dessas palavras e, para todas as palavras que não estão entre as 236 mais comuns, o comprimento.dicttries
contém, para cada comprimento de palavra, uma árvore de decisão que contém todas as palavras restantes. Nessas tentativas, as entradas são selecionadas à medida que avançamos.Dessa forma, alcançamos uma taxa de erro significativamente menor do que todos os outros esquemas com perdas; infelizmente, o
wordsseq
arquivo ainda é grande demais para ser competitivo.Aqui está uma versão completa que cria os arquivos e faz a análise:
fonte
C ++ (WIP), 1923 * 2 + 1017344 = 1021190
Tal como está, esta solução é WIP e, portanto, não destruída. Também considerando que o tamanho real do código mal tem impacto na pontuação, pensei em postar minha resposta antes de começar a otimizá-la.
(Código completo disponível aqui: https://github.com/BrainStone/MobyDickRNG - Inclui programa completo e pesquisa de sementes)
Esta solução é baseada em um RNG. Primeiro analiso o texto. Crio um mapa que conta as ocorrências de dois caracteres consecutivos. Então eu crio um mapa de distribuição. Tudo isso é feito estaticamente, portanto, deve estar de acordo com as regras.
Então, ao tentar imprimir o texto, faço uma pesquisa e seleciono um caracter aleatório dos possíveis. Embora isso geralmente produza resultados piores do que apenas a saída da carta seguinte mais comum, é provável que haja sementes divinas que produzam melhores resultados. É por isso que a semente é codificada. Atualmente, estou procurando a melhor semente. E atualizarei esta resposta assim que encontrar melhores sementes. Portanto, fique informado!
Se alguém quiser procurar sementes por conta própria ou usar RNGs diferentes, fique à vontade para fazer o repo.
Método usado para calcular a pontuação: https://github.com/BrainStone/MobyDickRNG/blob/master/src/search.cpp#L15
Observe que, embora a pontuação total seja a pior no momento, ela supera a contagem de erros de apenas espaços de saída. E as chances são boas de que a pontuação caia, verificando mais sementes.
Changelog
Sementes verificadas: 0-50000. Pontuação: 2305 * 2 + 1017754 = 1022364
Sementes verificadas: 0-80000. Pontuação: 1920 * 2 + 1017754 = 1021594 (-770)
Sementes verificadas: 0-32000000. Pontuação: 1923 * 2 + 1017344 = 1021190 (-404)
fonte
Ruby, 1164418 (ai)
Eu só queria ver o quão bem eu poderia fazer sem verificar outras respostas.
Não tenho certeza se isso é permitido, pois inclui um literal que eu gerei através da análise do arquivo, mas mesmo que não fosse, não era como se estivesse correndo o risco de bater em alguém.
Como eu gerei
x
Primeiro, eu gerei
a.txt
com o seguinte:Então eu gerei
a.csv
:Então eu o analisei
x
com o seguinte script Ruby:Como eu marquei
fonte
Python 3 , (146 * 2 + 879757) 880049 bytes
Experimente online!
Tabela de frequências bastante simples. Cada posição na sequência corresponde ao código ascii do caractere atual (menos 10 = 0x0a = '\ n', o caractere mais baixo do arquivo) e o caractere em cada índice é o próximo caractere mais frequente. Supondo que calculei as frequências corretamente ...
Testado com o código do teste de user202729
fonte
def f(c):return(" ">c)*c or"t ... e"[ord(c)-32]
?[Python 3] (644449 * 2 + 0) 1288898 pontos
Precisão perfeita em apenas 644449 bytes
O código completo não pode caber em uma resposta, então eu o coloquei aqui e substitui a grande cadeia de caracteres binária literal por b '###' no texto da resposta.
Isso é gerado com o código a seguir, onde "modified.py" é o arquivo gerado e "cheatsheet.txt" é o arquivo whale2.txt que inicia no segundo caractere.
O código pode ser executado adicionando o seguinte ao final de "modified.py". "whale2.txt" deve estar no mesmo diretório que "modified.py" e a saída será gravada em "out.txt".
Esta resposta não acessa diretamente whale.txt ou whale2.txt. Ele utiliza bibliotecas de compactação padrão existentes, conforme explicitamente permitido nas regras.
fonte