Codificação eficiente e sem erros * [fechada]

20

A missão

Como é sabido, o material genético de todas as criaturas conhecidas na Terra é codificado no DNA; usando os quatro nucleotídeos adenina, timina, citosina e guanina. (Comumente representado pela ATGC).

É claro que um bioinformaticista que deseja armazenar um genoma inteiro não deseja armazená-lo como ASCII, porque cada opção pode ser representada por meros dois bits!

A especificação

Sua missão, se você aceitar, é escrever um par de programas, funções ou métodos para converter uma representação ASCII em uma representação binária e vice-versa; representando Acomo b00, Tcomo b01, Gcomo b10e Ccomo b11(doravante "unidades").

Além disso, os bits altos de cada byte devem conter a contagem de unidades no byte, fazendo com que cada byte represente um trio.

Por exemplo: "GATTACCA"torna - se b11 100001 b11 010011 b10 1100xx.

No ASCII para entrada binária, espaços, tabulações e novas linhas devem ser ignoradas. Qualquer caractere que não esteja no conjunto de [ \r\n\tATGC]é um erro e pode ser ignorado ou encerrar o processamento.

Na entrada binária para ASCII, os bytes cujos dois bits altos são b00podem ser ignorados.

A saída ASCII pode conter espaços em branco; mas nunca deve ter mais que 4 vezes o tamanho da entrada binária mais um byte e deve terminar com uma nova linha.

A saída binária pode conter um número arbitrário de b00xxxxxxbytes de "controle"; mas nunca deve ser maior que a entrada ASCII.

Cada programa de conversão deve suportar entrada de comprimento arbitrário; e deve concluir a codificação ou decodificação em tempo aproximadamente linear.

A torção

Infelizmente para o bioinformaticista para quem você está realizando essa tarefa, ele de alguma forma prejudicou você, em um nível pessoal, mas talvez mesquinho.

Talvez ele tenha saído com sua irmã uma vez e nunca mais a tenha ligado. Talvez ele tenha pisado no rabo do seu cachorro. Os detalhes não são realmente importantes.

O importante é que você tenha uma chance de retorno!

Os detalhes

Cada conversão deve apresentar uma pequena taxa de erro; na ordem de um erro por dez mil a um milhão de unidades processadas.

Um erro pode ser um dos seguintes:

  • Erros de duplicação: "GAT TAC CA"torna - se"GAT TAA CCA"
  • Erros de exclusão: "GAT TAC CA"torna - se"GAT TAC A"
  • Erros de tradução: "GAT TAC CA"torna - se"GTA TAC CA"
  • Duplicações Triplet: "GAT TAC CA"torna-se"GAT TAC TAC CA"
  • Deslizamentos triplos: "GAT TAC CA"torna - se"TAC GAT CA"
  • Reversões de trigêmeos: "GAT TAC CA"torna - se"GAT CAT CA"

É claro que os erros serão introduzidos não devem ser imediatamente óbvios no código; e independentemente do comprimento da entrada; a conversão deve apresentar pelo menos um erro.

Duas execuções com entradas idênticas não devem necessariamente produzir saídas idênticas.

O truque

O vil bioinformaticista é um codificador moderadamente competente; e, como tal, algumas construções serão descobertas automaticamente e, como tal, são banidas:

  • Ele descobrirá automaticamente todas as chamadas para geradores de números aleatórios do sistema, como rand (), random () ou leitura de / dev / urandom ou / dev / random (ou qualquer que seja o idioma equivalente).
  • Ele também notará variáveis ​​supérfluas, contadores ou loops.

A pontuação

O codificador e o decodificador serão pontuados individualmente.

Cada um deles será executado três vezes em um conjunto de 100 arquivos de entrada gerados aleatoriamente, cada arquivo com um tamanho da ordem de 3 milhões de unidades.

Os dados para os casos de teste do codificador serão criados aproximadamente da seguinte maneira:

for (l = 1 => bigNum)
  for (t = 1 => 20)
    random_pick(3,ATGC)
    t == 20 ? newline : space

Os dados para os casos de teste do decodificador serão criados aproximadamente da seguinte maneira:

for (u = 1 => bigNum)
  for (t = 1 => 20)
    random_byte() | 0b11000000
   0x00

O codificador

  • Cada byte ausente do comprimento mínimo esperado no comprimento real receberá -1 pontos, até um máximo de -1000. (O comprimento mínimo esperado é ceil(count(ATGC) / 3).)

O decodificador

  • Cada byte sobre o comprimento máximo esperado no comprimento real receberá -1 pontos, até um máximo de -1000. (O comprimento máximo esperado é size(input) * 4 + 1.)

Ambos

  • Cada tipo de erro que pode ser produzido terá 100 pontos; para um total de 600 pontos possíveis para cada um, 1200 no total.
  • Cada caso de teste para o qual o codificador produz mais de 30% mais ou menos erros do que sua própria média será penalizado em -5 pontos.
  • Cada caso de teste para o qual o codificador produz menos que 15% mais ou menos erros do que sua própria média receberá 5 pontos.
  • Cada caso de teste em que as três execuções produzem saídas idênticas será penalizado por -10 pontos.

Requisitos difíceis

Uma entrada será desqualificada se:

  • Para qualquer entrada válida com mais de um trigêmeo, ela falha em produzir um único erro.
  • Seu desempenho é tal que não pode concluir a luva de teste dentro de aproximadamente uma hora.
  • Em média, produz mais de um erro em cada dez mil unidades.
  • Em média, produz menos de um erro em cada milhão de unidades.

A interface

Os participantes devem aceitar entrada na entrada padrão e saída para a saída padrão.

Se a entrada for um programa com dupla função; os comutadores -ee -ddeve definir o programa para codificação e decodificação, respectivamente.

Invocações de exemplo:

$ encoder <infile.txt >outfile.bin
$ decoder <infile.bin >outfile.txt
$ recoder -e <infile.txt >outfile.bin

O vencedor

O vencedor é a entrada com a maior pontuação; o máximo teórico sendo 1200 para tipos de erros mais 3000 pontos para estabilidade na taxa de geração de erros.

No improvável evento de empate; o vencedor será determinado pela contagem de votos.

As notas adicionais

Para fins de execução da luva de teste, cada entrada deve incluir instruções de execução ou compilação.

Todas as entradas devem, de preferência, ser executáveis ​​em uma máquina Linux sem o X.

Williham Totland
fonte
4
A etiqueta foi alterada. KotH é para desafios em que os envios interagem entre si. Também temo que seja difícil ou impossível impor o componente "secreto" objetivamente.
Martin Ender
2
Concordo com o comentário de @ m.buettner de que a parte secreta é difícil de julgar. Por outro lado, sinto que esta é a única parte interessante do desafio. Posso garantir que a geração e a taxa de erros estejam exatamente dentro das especificações e, portanto, tenham o máximo de pontos. Ou eu sinto falta de algo da especificação? Além disso, se um tipo adicional de erro for aceito, ele será adicionado à lista acima; e todas as respostas serão pontuadas nele. Parece que você vai mudar o desafio depois que as pessoas começarem a trabalhar ou enviar soluções, o que eu acho que não é uma boa ideia.
Howard
@ Howard: Notável. As regras são atualizadas com critérios específicos de desvios; e o aspecto mutacional errado. erros é removido.
Williham Totland
1
Vou dar a minha resposta .. mas acho que as duas frases "Cada conversão deve introduzir uma pequena taxa de erro; da ordem de um erro por dez mil a um milhão de unidades processadas". e "Uma entrada será desqualificada se: Em média, produz mais de um erro em cada dez mil unidades". são incompatíveis. O mesmo entre "Cada conversão deve introduzir uma pequena taxa de erro; da ordem de um erro por dez mil a um milhão de unidades processadas". e "Uma entrada será desqualificada se: Em média, produzir menos de um erro em cada milhão de unidades".
Mattsteel 24/07/2015
1
Estou votando para encerrar esta questão como fora de tópico, porque os desafios secretos não estão mais no tópico neste site. meta.codegolf.stackexchange.com/a/8326/20469
cat

Respostas:

3

Perl 5.10

Fico feliz em apresentar minha solução em Perl.

Quando iniciei o desafio, tinha certeza de que Perl ficaria bem abaixo do limite de 1 hora.

Para fins de teste, desenvolvi um gerador de amostra simples e um gerador de amostra codificado.

Depois, desenvolvi o codificador que teve um esforço maior e produziu um código mais longo. O codificador funciona da seguinte maneira:

  1. O primeiro loop lê o arquivo inteiro e divide os dados para ter uma matriz de todos os trigêmeos
  2. O segundo loop percorre a matriz e precede a cada elemento seu comprimento
  3. o terceiro loop percorre novamente e mapeia cada caractere para fornecer a saída.

A saída binária codificada é formatada como "linhas" terminadas em nova linha de 20 octetos, em que cada octeto codifica um tripleto, com dois caracteres de prefixo (como um número de linha cíclico).

por exemplo, a entrada mais curta de três bytes:

AAA

deve fornecer a saída mais curta de três bytes mais a nova linha.

00ÿ

isso é

30 30 FF 0A

e

AGG CGC AAC GGC TAA ATC GTT TTC ACA CCA CGT TTG AAA CGG GTG ACA CGA GAT TTA GTC
TAT GGT ACT AGG TAC GCC GTG GTG CGT GCG GAG TTA CTA GAT GTG TTA GTA CGC CAT CGT

deve dar o seguinte binário.

01ÊûÃëÐÇå×ÌüùÖÀúæÌøáÔç
00ÑéÍÊÓïææùîâÔôáæÔäûñù

Deve por causa da pequena taxa de erro: Para a menor entrada, o script apresenta 1 erro.

Para um arquivo de três milhões de trigêmeos, o codificador apresenta 11 erros.

Desde que o script seja dnacodec3.pl, a execução é invocada no prompt de comando, como de costume:

$> perl dnacodec3.pl -e < plain.txt > coded.txt

O decodificador funciona da seguinte maneira:

  1. O primeiro loop lê o arquivo inteiro e divide os dados para ter uma matriz de todos os octetos. Ele acompanha todas as novas linhas.
  2. o segundo loop examina cada octeto, mantendo aqueles que não começam com 00 e desconsidera o restante. A saída Ascii simples é formatada como linhas terminadas em nova linha de trigêmeos, separadas por um espaço. A nova linha está na mesma posição em que estava na entrada.

Eu preparei um arquivo de teste de amostra de 3 milhões de trigêmeos (cerca de 12MByte) e testei o tempo. Usando meu laptop com um Intel Core i5 vPro a 2,6 GHz, a execução do codificador 3M sempre leva menos de 20 segundos. Durante a execução, são necessários 200-220 MByte de RAM. Que desperdício!

A execução da decodificação leva menos de 10 segundos. Não pode introduzir erros ... por enquanto.

Novamente, para a execução da decodificação

$> perl dnacodec3.pl -d < coded.txt > plain.txt

Aqui está o código

#!/usr/bin/perl
use strict ;
use warnings ;
my $switch = shift || die "usage $0 [-e|-d]\n";
my %map = qw( x 10  X 11  c 0b  ? 00
              A 00  T 01  G 10  C 11  
              0 00  1 01  2 10  3 11  
              00 A  01 T  10 G  11 C  ) ;
my $r = 20 ;
my @dummy = unpack ( '(A4)*', '0xxx' x $r ) ;
my $map = oct( $map{ c } . ($map{ C } x 9) ) ;
my $t = time() ;
my @inp = () ;
my @out = () ;
my @buf = () ;
my $n ;

sub arch {
    push @buf, @dummy[ 0 .. $r - $#buf - 2 ] ;
    push @out, "@buf" ;
    @buf = () ;
}

sub encode {
    my $mask = '(A3)*' ;
    while ( my $row = <STDIN> ) {
        chomp $row ;
        $row =~ s/\s+//g ;
        $row =~ s/[^\r\n\tATGC]//g ;
        next unless $row ;
        my @row = unpack( $mask, $row ) ;
        push @inp, @row if $row ;
    }
    $n = scalar @inp ;
    $r = $n if $r > $n ;
    for ( my $i = $n - 1 ; $i >= 0 ; --$i ) {
        my $e = $inp[$n-$i-1] ;
        my $l = length( $e ) ;
        my $d = $e =~ /\?/? 0: $l ;
        push @buf, ( $d -((($i-($n>>1))&$map)?0:1) )
           . $e . 'x'x(3-$l) ;
        arch unless $i % $r ;
    }
    arch if scalar @buf ;
    my $m = scalar @out ;
    for ( my $j = $m - 1 ; $j >= 0; --$j ) {
        my @ary = () ;
        my $e = $out[$m-$j-1] ;
        for my $byte ( split / /, $e ) {
            my @byte = split ( //, $byte ) ;
            my @trad = map { $map{ $_ } } @byte ;
            my $byte = join( '', @trad ) ;
            push @ary, $byte ;
        };
        my $row = sprintf( '%02d', $j % $r) ;
        $row .= pack( '(B8)*', @ary ) ;
        print "$row\n" ;
    }
}

sub decode {
    my $mask = '(B8)*' ;
    while ( my $row = <STDIN> ) {
        chomp $row ;
        next unless $row ;
        my @row = unpack( $mask, $row ) ;
        push @inp, @row[0..$#row], '?' if $row ;
    }
    $n = scalar @inp ;
    my @ary = () ;
    for ( my $i = $n - 1 ; $i >= 0 ; --$i ) {
        my $e = $inp[$n-$i-1] ;
        if ( $e ne '?' ) {
            my $u = oct( '0b'. substr($e,0,2) ) ;
            my $a = '' ;
            for my $j ( 1 .. $u ) {
                $a .= $map{ substr($e,$j+$j,2) } ;
            }
            push @ary, $a if $u ;
        }
        else {
            my $row = "@ary" ;
            $row =~ s/\s{2,}//g ;
            print "$row\n" if $row ;
            @ary =() ;
        }
    }
}

decode if $switch eq '-d' ;
encode if $switch eq '-e' ;

E aqui está o gerador de amostra:

sub test_coder {
    my $n = shift || 1000 ;
    my @b = qw( A C G T ) ;
    for (my $l = 0; $l < $n; $l++) {
        my @ary = () ;
        for (my $t = 0; $t < 20; $t++) {
            push @ary, $b[ int(rand(4)) ] . $b[ int(rand(4)) ] . $b[ int(rand(4)) ] ;
        }
        print "@ary\n" ;
    }
    1;
}

sub test_decoder {
    my $n = shift || 1000;
    for (my $l = 0; $l < $n; $l++) {
        my @ary = () ;
        for (my $t = 0; $t < 20; $t++) {
            push @ary, int(rand(256)) | 0b11000000 ;
        }
        my $row = pack( 'C*', @ary ) ;
        print "$row\000" ;
    }
    1;
}


test_coder( @ARGV ) if $switch eq '-g' ;
test_decoder( @ARGV )  if $switch eq '-h' ;
Mattsteel
fonte
Esqueci de mostrar onde o erro foi injetado: o push @buf no segundo loop faz o truque.
Mattsteel
É sutil, eu vou te dar isso. Não executarei testes em larga escala até que haja mais de um concorrente, mas isso é bom. :)
Williham Totland
Obrigado. Eu sei que isto é uma sugestão para outros amigos ... Eu gostaria de melhorar a aleatoriedade da posição de erro usando o (ainda não utilizado) func tempo: a partir executado em segundos pares ou ímpares deve render saída diferente
Mattsteel