Codificación eficiente sin errores * [cerrado]

20

La misión

Como es bien sabido, el material genético de todas las criaturas conocidas en la Tierra está codificado en el ADN; utilizando los cuatro nucleótidos adenina, timina, citosina y guanina. (Comúnmente representado por ATGC).

Un bioinformático que desee almacenar un genoma completo, por supuesto, no querría almacenar esto como ASCII, ¡porque cada elección puede representarse con solo dos bits!

La especificación

Su misión, si elige aceptarla, es escribir un par de programas, funciones o métodos para convertir una representación ASCII en una representación binaria y viceversa; representando Aas b00, Tas b01, Gas b10y Cas b11(en adelante "unidades").

Además, los bits altos de cada byte deben contener el recuento de unidades en el byte, haciendo que cada byte represente un triplete.

Por ejemplo: se "GATTACCA"convierte b11 100001 b11 010011 b10 1100xx.

En el ASCII a la entrada binaria, los espacios, las pestañas y las nuevas líneas deben ignorarse. Cualquier carácter que no esté en el conjunto de [ \r\n\tATGC]es un error y puede ignorarse o finalizar el procesamiento.

En la entrada binaria a ASCII, los bytes cuyos dos bits altos son b00ignorados.

La salida ASCII puede contener espacios en blanco; pero nunca debe ser más de 4 veces el tamaño de la entrada binaria más un byte de largo, y debe terminar con una nueva línea.

La salida binaria puede contener un número arbitrario de b00xxxxxxbytes de "control"; pero nunca debe ser más largo que la entrada ASCII.

Cada programa de conversión debe admitir entradas de longitud arbitraria; y debe completar la codificación o decodificación en aproximadamente tiempo lineal.

El giro

Desafortunadamente para el bioinformista para quien está realizando esta tarea, de alguna manera lo ha perjudicado, a nivel personal pero quizás mezquino.

Quizás salió con tu hermana una vez, y nunca la volvió a llamar. Quizás pisó la cola de tu perro. Los detalles no son realmente importantes.

¡Lo importante es que tenga la oportunidad de pagar!

Los detalles

Cada conversión debe introducir una pequeña tasa de error; en el orden de un error por cada diez mil a un millón de unidades procesadas.

Un error puede ser uno de los siguientes:

  • Errores de duplicación: se "GAT TAC CA"convierte"GAT TAA CCA"
  • Errores de eliminación: se "GAT TAC CA"convierte en"GAT TAC A"
  • Errores de traducción: se "GAT TAC CA"convierte"GTA TAC CA"
  • Duplicaciones de triplete: se "GAT TAC CA"convierte en"GAT TAC TAC CA"
  • Deslizamientos de triplete: se "GAT TAC CA"convierte en"TAC GAT CA"
  • Triplet reversiones: se "GAT TAC CA"convierte"GAT CAT CA"

Que los errores se introducirán, por supuesto, no debería ser inmediatamente obvio por el código; e independientemente de la longitud de la entrada; La conversión debe introducir al menos un error.

Dos ejecuciones con entradas idénticas no deberían producir necesariamente salidas idénticas.

El truco

El vil bioinformático es un codificador moderadamente competente; y como tal, algunas construcciones se descubrirán automáticamente y, como tales, se prohibirán:

  • Descubrirá automáticamente cualquier llamada a los generadores de números aleatorios del sistema, como rand (), random () o lectura de / dev / urandom o / dev / random (o el equivalente en el idioma).
  • También notará las variables superfluas, contadores o bucles.

La puntuación

El codificador y el decodificador se puntuarán individualmente.

Cada uno se ejecutará 3 veces contra un conjunto de 100 archivos de entrada generados aleatoriamente, cada archivo con un tamaño del orden de 3 millones de unidades.

Los datos para los casos de prueba del codificador se crearán aproximadamente de la siguiente manera:

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

Los datos para los casos de prueba del decodificador se crearán aproximadamente de la siguiente manera:

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

El codificador

  • Cada byte que falte de la longitud mínima esperada en la longitud real obtendrá -1 puntos, hasta un máximo de -1000. (La longitud mínima esperada es ceil(count(ATGC) / 3)).

El decodificador

  • Cada byte sobre la longitud máxima esperada en la longitud real obtendrá -1 puntos, hasta un máximo de -1000. (La longitud máxima esperada es size(input) * 4 + 1).

Ambos

  • Cada tipo de error que pueda producirse obtendrá 100 puntos; para un total de 600 puntos posibles para cada uno, 1200 en total.
  • Cada caso de prueba para el cual el codificador produce más del 30% más o menos errores que su propio promedio será penalizado por -5 puntos.
  • Cada caso de prueba para el cual el codificador produce menos del 15% más o menos errores que su propio promedio recibirá 5 puntos.
  • Cada caso de prueba donde las tres ejecuciones produzcan salidas idénticas será penalizado por -10 puntos.

Requisitos duros

Una entrada será descalificada si:

  • Para cualquier entrada válida de más de un triplete, no produce ni siquiera un error.
  • Su rendimiento es tal que no puede completar el guante de prueba en aproximadamente una hora.
  • En promedio, produce más de un error por cada diez mil unidades.
  • En promedio produce menos de un error en cada millón de unidades.

La interfaz

Los participantes deben aceptar la entrada en la entrada estándar y la salida a la salida estándar.

Si la entrada es un programa con doble función; los interruptores -ey -ddeberían configurar el programa para codificar y decodificar, respectivamente.

Invocaciones de ejemplo:

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

El ganador

El ganador es la entrada con la puntuación más alta; el máximo teórico es 1200 para tipos de error más 3000 puntos para estabilidad en la tasa de generación de errores.

En el caso improbable de un empate; El ganador se determinará por recuento de votos.

Las notas adicionales

Para ejecutar el guante de prueba, cada entrada debe incluir instrucciones de ejecución o compilación.

Todas las entradas deben ejecutarse preferiblemente en una máquina Linux sin X.

Williham Totland
fuente
44
Cambió la etiqueta. KotH es para desafíos donde las presentaciones interactúan entre sí. Además, me temo que será difícil o imposible hacer cumplir el componente "discreto" de manera objetiva.
Martin Ender
2
Estoy de acuerdo con el comentario de @ m.buettner de que la parte oculta es difícil de juzgar. Por otro lado, siento que esta es la única parte interesante del desafío. Puedo garantizar que la generación de errores y la tasa están exactamente dentro de las especificaciones y, por lo tanto, tienen los puntos máximos. ¿O extraño algo de la especificación? Además, si se acepta un tipo adicional de error, se agregará a la lista anterior; y todas las respuestas se puntuarán en él. Parece que va a cambiar el desafío después de que la gente comenzó a trabajar o presentar soluciones, lo que creo que no es una buena idea.
Howard
@Howard: señaló. Las reglas se actualizan con criterios específicos de falta de personal; y el aspecto mutacional wrt. Se eliminan los errores.
Williham Totland
1
Voy a dar mi respuesta ... pero creo que las dos oraciones "Cada conversión debería introducir una pequeña tasa de error; del orden de un error por cada diez mil a un millón de unidades procesadas". y "Una entrada será descalificada si: En promedio produce más de un error por cada diez mil unidades". son incompatibles Lo mismo entre "Cada conversión debe introducir una pequeña tasa de error; del orden de un error por cada diez mil a un millón de unidades procesadas". y "Una entrada será descalificada si: En promedio produce menos de un error en cada millón de unidades".
Mattsteel
1
Estoy votando para cerrar esta pregunta como fuera de tema porque los desafíos poco claros ya no están en el tema en este sitio. meta.codegolf.stackexchange.com/a/8326/20469
gato

Respuestas:

3

Perl 5.10

Me alegra presentar mi solución en Perl.

Cuando comencé el desafío, estaba bastante seguro de que Perl se mantendría muy por debajo del límite de 1 hora.

Para fines de prueba, desarrollé un generador de muestra simple y un generador de muestra codificado.

Luego desarrollé el codificador que tomó un mayor esfuerzo y produjo un código más largo. El codificador funciona de la siguiente manera:

  1. primer bucle lee todo el archivo y divide los datos para tener una matriz de todos los tripletes
  2. el segundo bucle atraviesa la matriz y antepone a cada elemento su longitud
  3. el tercer bucle atraviesa nuevamente y asigna cada carácter para dar la salida.

La salida binaria codificada tiene el formato de "líneas" terminadas en una nueva línea de 20 octetos, donde cada octeto codifica un triplete, con dos caracteres de prefijo (como un número de línea cíclico).

por ejemplo, la entrada más corta de tres bytes:

AAA

debería dar la salida más corta de tres bytes más nueva línea.

00ÿ

es decir

30 30 FF 0A

y

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

debería dar el siguiente binario.

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

Debería debido a la pequeña tasa de error: para la entrada más pequeña, el script introduce 1 error.

Para una ejecución de archivo de 3 millones de tripletes, el codificador presenta 11 errores.

Siempre que el script sea dnacodec3.pl, la ejecución se invoca en el símbolo del sistema como de costumbre:

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

El decodificador funciona de la siguiente manera:

  1. El primer bucle lee todo el archivo y divide los datos para tener una matriz de todos los octetos. Realiza un seguimiento de cada nueva línea.
  2. El segundo bucle examina cada octeto, conserva los que no comienzan con 00 y no tiene en cuenta el resto. La salida de Ascii simple está formateada como líneas de tripletas terminadas en una nueva línea separadas por un espacio. La nueva línea está en la misma posición que en la entrada.

Preparé un archivo de prueba de muestra de 3 millones de tripletas (aproximadamente 12 MByte) y probé el tiempo. Al usar mi computadora portátil con un Intel Core i5 vPro a 2.6 GHz, la ejecución del codificador 3M siempre toma menos de 20 segundos. Durante la ejecución toma 200-220 MByte de RAM. ¡Que desperdicio!

La ejecución de decodificación lleva menos de 10 segundos. No puede introducir errores ... por ahora.

De nuevo, para la ejecución de decodificación

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

Aqui esta el codigo

#!/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' ;

Y aquí está el generador de muestras:

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
fuente
Olvidé mostrar dónde se inyecta el error: el push @buf en el segundo bucle hace el truco.
Mattsteel
Es sutil, te lo daré. No realizaré pruebas a gran escala hasta que haya más de un competidor, pero esto es algo bueno. :)
Williham Totland
Gracias. Sé que esto es una sugerencia para otros amigos ... Me gustaría mejorar la aleatoriedad de la posición de error usando la función de tiempo (aún sin usar): comenzar a correr en segundos pares o impares debería producir una salida diferente
Mattsteel