Encodage efficace sans erreur * [fermé]

20

La mission

Comme cela est bien connu, le matériel génétique de toutes les créatures connues sur Terre est codé dans l'ADN; en utilisant les quatre nucléotides adénine, thymine, cytosine et guanine. (Communément représenté par ATGC).

Un bioinformaticien souhaitant stocker un génome entier ne voudra bien sûr pas le stocker en ASCII, car chaque choix peut être représenté par seulement deux bits!

La spécification

Votre mission, si vous l'acceptez, est d'écrire une paire de programmes, de fonctions ou de méthodes pour convertir une représentation ASCII en représentation binaire et inversement; représentant Acomme b00, Tcomme b01, Gcomme b10et Ccomme b11(désormais "unités").

De plus, les bits de poids fort de chaque octet doivent contenir le nombre d'unités de l'octet, ce qui fait que chaque octet représente un triplet.

Par exemple: "GATTACCA"devient b11 100001 b11 010011 b10 1100xx.

Dans l'entrée ASCII vers binaire, les espaces, les tabulations et les retours à la ligne doivent être ignorés. Tout caractère ne figurant pas dans l'ensemble de [ \r\n\tATGC]est une erreur et peut être ignoré ou terminer le traitement.

Dans l'entrée binaire vers ASCII, les octets dont les deux bits de poids fort sont b00ignorés.

La sortie ASCII peut contenir des espaces; mais ne doit jamais dépasser 4 fois la taille de l'entrée binaire plus un octet de long et doit se terminer par une nouvelle ligne.

La sortie binaire peut contenir un nombre arbitraire d' b00xxxxxxoctets de "contrôle"; mais ne doit jamais être plus longue que l'entrée ASCII.

Chaque programme de conversion doit prendre en charge des entrées de longueur arbitraire; et devrait terminer le codage ou le décodage en un temps approximativement linéaire.

La torsion

Malheureusement pour le bioinformaticien pour lequel vous effectuez cette tâche, il vous a en quelque sorte fait du tort, à un niveau personnel mais peut-être mesquin.

Peut-être est-il sorti une fois avec votre sœur et ne l'a plus jamais appelée. Peut-être qu'il a marché sur la queue de votre chien. Les détails ne sont pas vraiment importants.

Ce qui est important, c'est que vous ayez une chance de récupération!

Les détails

Chaque conversion doit introduire un petit taux d'erreur; de l'ordre d'une erreur par dix mille à un million d'unités traitées.

Une erreur peut être l'une des suivantes:

  • Erreurs de duplication: "GAT TAC CA"devient"GAT TAA CCA"
  • Erreurs de suppression: "GAT TAC CA"devient"GAT TAC A"
  • Erreurs de traduction: "GAT TAC CA"devient"GTA TAC CA"
  • Duplications en triplets: "GAT TAC CA"devient"GAT TAC TAC CA"
  • Glissements de triplets: "GAT TAC CA"devient"TAC GAT CA"
  • Inversion de triplet: "GAT TAC CA"devient"GAT CAT CA"

Le fait que des erreurs soient introduites ne doit bien sûr pas être immédiatement évident dans le code; et quelle que soit la longueur de l'entrée; la conversion doit introduire au moins une erreur.

Deux passages avec des entrées identiques ne devraient pas nécessairement produire des sorties identiques.

L'astuce

Le vil bioinformaticien est un codeur moyennement compétent; et en tant que tel, certaines constructions seront automatiquement découvertes et sont en tant que telles interdites:

  • Il découvrira automatiquement tous les appels aux générateurs de nombres aléatoires du système, tels que rand (), random (), ou lisant dans / dev / urandom ou / dev / random (ou quel que soit l'équivalent linguistique).
  • Il remarquera également toutes les variables, compteurs ou boucles superflus.

La notation

L'encodeur et le décodeur seront notés individuellement.

Chacun sera exécuté 3 fois contre un ensemble de 100 fichiers d'entrée générés aléatoirement, chaque fichier ayant une taille de l'ordre de 3 millions d'unités.

Les données pour les cas de test de l'encodeur seront créées approximativement comme suit:

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

Les données pour les cas de test du décodeur seront créées approximativement comme suit:

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

L'encodeur

  • Chaque octet manquant de la longueur minimale attendue dans la longueur réelle marquera -1 points, jusqu'à un maximum de -1000. (La longueur minimale attendue est ceil(count(ATGC) / 3).)

Le décodeur

  • Chaque octet sur la longueur maximale attendue dans la longueur réelle marquera -1 point, jusqu'à un maximum de -1000. (La longueur maximale attendue est size(input) * 4 + 1.)

Tous les deux

  • Chaque type d'erreur qui peut être produit marquera 100 points; pour un total de 600 points possible pour chacun, 1200 au total.
  • Chaque cas de test pour lequel l'encodeur produit plus de 30% d'erreurs en plus ou en moins que sa propre moyenne sera pénalisé de -5 points.
  • Chaque cas de test pour lequel l'encodeur produit moins de 15% d'erreurs en plus ou en moins que sa propre moyenne recevra 5 points.
  • Chaque cas de test où les trois séries produisent des sorties identiques sera pénalisé de -10 points.

Exigences strictes

Une inscription sera disqualifiée si:

  • Pour toute entrée valide de plus d'un triplet, elle ne produit même pas une erreur.
  • Ses performances sont telles qu'il ne peut pas terminer le gant de test dans environ une heure.
  • Il produit en moyenne plus d'une erreur sur dix mille unités.
  • Il produit en moyenne moins d'une erreur par million d'unités.

L'interface

Les participants doivent accepter l'entrée sur l'entrée standard et la sortie sur la sortie standard.

Si l'entrée est un programme à double fonction; les commutateurs -eet -ddevraient définir le programme pour le codage et le décodage, respectivement.

Exemples d'appels:

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

Le gagnant

Le gagnant est l'entrée avec le score le plus élevé; le maximum théorique étant de 1200 pour les types d'erreur plus 3000 points pour la stabilité du taux de génération d'erreur.

Dans le cas peu probable d'un match nul; le gagnant sera déterminé par le décompte des voix.

Les notes supplémentaires

Aux fins de l'exécution du test gant, chaque entrée doit inclure des instructions d'exécution ou de compilation.

Toutes les entrées doivent de préférence être exécutables sur une machine Linux sans X.

Williham Totland
la source
4
Modification de la balise. KotH est pour les défis où les soumissions interagissent les unes avec les autres. Je crains également qu'il soit difficile, voire impossible, d'appliquer objectivement le composant "sournois".
Martin Ender
2
Je suis d'accord avec le commentaire de @ m.buettner selon lequel la partie sournoise est difficile à juger. D'un autre côté, je pense que c'est la seule partie intéressante du défi. Je peux garantir que la génération et le taux d'erreur sont exactement dans les spécifications et ont donc le maximum de points. Ou est-ce que je manque quelque chose de la spécification? De plus, si un type d'erreur supplémentaire est accepté, il sera ajouté à la liste ci-dessus; et toutes les réponses y seront notées. on dirait que vous allez changer le défi après que les gens ont commencé à travailler ou à soumettre des solutions qui, je pense, ne sont pas une bonne idée.
Howard
@Howard: noté. Les règles sont mises à jour avec des critères de sous-traitance spécifiques; et l'aspect mutationnel wrt. les erreurs sont supprimées.
Williham Totland
1
Je vais donner ma réponse .. mais je pense que les deux phrases "Chaque conversion devrait introduire un petit taux d'erreur; de l'ordre d'une erreur par dix mille à un million d'unités traitées." et "Une inscription sera disqualifiée si: Elle produit en moyenne plus d'une erreur sur dix mille unités." sont incompatibles. La même chose entre "Chaque conversion devrait introduire un petit taux d'erreur; de l'ordre d'une erreur pour dix mille à un million d'unités traitées." et "Une inscription sera disqualifiée si: Elle produit en moyenne moins d'une erreur par million d'unités."
Mattsteel
1
Je vote pour fermer cette question comme hors sujet car les défis sournois ne sont plus sur le sujet sur ce site. meta.codegolf.stackexchange.com/a/8326/20469
cat

Réponses:

3

Perl 5.10

Je suis heureux de présenter ma solution en Perl.

Quand j'ai commencé le défi, j'étais sûr que Perl resterait bien en dessous de la limite de 1 heure.

À des fins de test, j'ai développé un générateur d'échantillons simple et un générateur d'échantillons codés.

Ensuite, j'ai développé l'encodeur qui a demandé plus d'efforts et produit un code plus long. L'encodeur fonctionne comme suit:

  1. la première boucle lit le fichier entier et divise les données pour avoir un tableau de tous les triplets
  2. la seconde boucle parcourt le tableau et ajoute à chaque élément sa longueur
  3. la troisième boucle parcourt à nouveau et mappe chaque caractère pour donner la sortie.

La sortie binaire codée est ainsi formatée en "lignes" terminées par une nouvelle ligne de 20 octets où chaque octet code un triplet, avec deux caractères de préfixe (comme un numéro de ligne cyclique).

par exemple, l' entrée de trois octets la plus courte :

AAA

devrait donner la sortie la plus courte de trois octets plus une nouvelle ligne.

00ÿ

C'est

30 30 FF 0A

et

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

devrait donner le binaire suivant.

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

Devrait à cause du petit taux d'erreur: Pour la plus petite entrée, le script introduit 1 erreur.

Pour un fichier de 3 millions de triplets, l'encodeur introduit 11 erreurs.

À condition que le script soit dnacodec3.pl, l'exécution est invoquée à l'invite de commande comme d'habitude:

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

Le décodeur fonctionne comme suit:

  1. la première boucle lit le fichier entier et divise les données pour avoir un tableau de tous les octets. Il garde une trace de chaque nouvelle ligne.
  2. la seconde boucle examine chaque octet, en conservant ceux qui ne commencent pas par 00 et ignore le reste. La sortie simple Ascii est formatée comme des lignes de triplets terminées par une nouvelle ligne séparées par un espace. La nouvelle ligne est dans la même position que dans l'entrée.

J'ai préparé un échantillon de 3 millions de triplets (environ 12 Mo) et testé le timing. En utilisant mon ordinateur portable avec un Intel Core i5 vPro à 2,6 GHz, le fonctionnement de l'encodeur 3M prend toujours moins de 20 secondes. Pendant l'exécution, il faut 200 à 220 Mo de RAM. Quel gâchis!

Le cycle de décodage prend moins de 10 secondes. Il ne peut pas introduire d'erreurs ... pour l'instant.

Encore une fois, pour le décodage

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

Voici le code

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

Et voici le générateur d'échantillons:

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
la source
J'ai oublié de montrer où l'erreur est injectée: le push @buf dans la deuxième boucle fait l'affaire.
Mattsteel
C'est subtil, je vais te donner ça. Je ne ferai pas de tests à grande échelle tant qu'il n'y aura pas plus d'un concurrent, mais c'est une bonne chose. :)
Williham Totland
Merci. Je sais que c'est une suggestion pour d'autres amis ... Je voudrais améliorer le caractère aléatoire de la position d'erreur en utilisant la fonction de temps (encore inutilisée): le démarrage de la course à des secondes impaires ou paires devrait produire une sortie différente
Mattsteel