高效的无错误*编码


20

使命

众所周知,地球上所有已知生物的遗传物质都是用DNA编码的。使用四个核苷酸腺嘌呤,胸腺嘧啶,胞嘧啶和鸟嘌呤。(通常由ATGC表示)。

希望存储整个基因组的生物信息学家当然不希望将其存储为ASCII,因为每个选择只能用两位来表示!

规格

您的任务(如果您选择接受)是编写一对程序,函数或方法,以将ASCII表示形式转换为二进制表示形式,然后转换回二进制表示形式。表示Ab00Tas b01Gas b10Cas b11(此后称为“单位”)。

另外,每个字节的高位应包含该字节中的单位数,从而使每个字节代表一个三元组。

例如:"GATTACCA"成为b11 100001 b11 010011 b10 1100xx

在ASCII到二进制输入中,空格,制表符和换行符应忽略。不在此集合中的任何字符[ \r\n\tATGC]都是错误,可能会被忽略或终止处理。

在二进制到ASCII的输入中,b00可以忽略两个高位的字节。

ASCII输出可能包含空格;但绝对不能超过二进制输入的大小的4倍加一个字节长,并且必须以换行符结尾。

二进制输出可以包含任意数量的b00xxxxxx“控制”字节。但绝对不能长于ASCII输入。

每个转换程序必须支持任意长度的输入;并应在大约线性时间内完成编码或解码。

扭曲

不幸的是,对于您要执行此任务的生物信息学家而言,他在某种程度上还是在您个人层面或某些层面上委屈了您。

也许他曾经和你姐姐出去一次,却再也没有打电话给她。也许他踩了你狗的尾巴。具体细节并不是很重要。

重要的是您有机会获得回报!

细节

每次转换应引入较小的错误率;每处理一万到一百万个单位一个错误的数量级。

错误可能是以下之一:

  • 复制错误:"GAT TAC CA"变为"GAT TAA CCA"
  • 删除错误:"GAT TAC CA"变为"GAT TAC A"
  • 翻译错误:"GAT TAC CA"变成"GTA TAC CA"
  • 三元组重复:"GAT TAC CA"变为"GAT TAC TAC CA"
  • 三联体滑点:"GAT TAC CA"变为"TAC GAT CA"
  • 三元组反转:"GAT TAC CA"变为"GAT CAT CA"

当然,从代码中不会立即显而易见会引入错误;和输入的长度无关;转换应引入至少一个错误。

输入相同的两次运行不一定会产生相同的输出。

诀窍

卑鄙的生物信息学家是一位中等能力的编码人员;因此,将自动发现某些构造,因此被禁止:

  • 他将自动发现对系统随机数生成器的任何调用,例如rand(),random(),或从/ dev / urandom或/ dev / random中读取(或任何等效的语言)。
  • 他还将注意到任何多余的变量,计数器或循环。

得分

编码器和解码器将分别评分。

每个文件将针对一组100个随机生成的输入文件运行3次,每个文件的大小约为300万个单位。

编码器测试用例的数据将大致如此创建:

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

解码器测试用例的数据将大致如此创建:

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

编码器

  • 实际长度中预期的最小长度中缺少的每个字节将获得-1分,最高为-1000。(预期的最小长度为ceil(count(ATGC) / 3)。)

解码器

  • 实际长度中超出预期最大长度的每个字节将获得-1分,最高为-1000。(预期的最大长度为size(input) * 4 + 1。)

  • 每种可能产生的错误将获得100分;每个可能总共有600点,总共1200点。
  • 编码器产生的错误比其平均错误多出或少于30%的每个测试用例,将受到-5分的惩罚。
  • 每个编码器产生的错误比其平均误差少15%或更少的测试用例将获得5分。
  • 所有三个运行均产生相同输出的每个测试用例将受到-10分的惩罚。

硬性要求

符合以下条件的参赛作品将被取消参赛资格:

  • 对于任何有效的输入,如果输入的时间长于一个三元组,它甚至不会产生一个错误。
  • 其性能使它无法在大约一小时内完成测试手套。
  • 平均每万个单位产生一个以上的错误。
  • 平均每百万个单位产生少于一个错误。

介面

参赛者应在标准输入上接受输入,然后输出到标准输出。

如果条目是一个具有双重功能的程序;开关-e-d应分别设置用于编码和解码的程序。

调用示例:

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

赢家

获胜者是得分最高的作品;错误种类的理论最大值为1200,错误产生率的稳定性为3000点。

万一发生平局;获胜者将由投票数决定。

附加说明

为了运行测试手套,每个条目都应包括运行或编译说明。

所有条目最好都可以在没有X的Linux机器上运行。


4
更改了标签。KotH适用于提交彼此交互的挑战。另外,恐怕很难客观地实施“不足”部分。
Martin Ender 2014年

2
我同意@ m.buettner的评论,即不足的部分很难判断。另一方面,我认为这是挑战中唯一有趣的部分。我可以保证错误生成和错误率完全在规格范围内,因此具有最大分数。还是我错过了一些规格?此外,如果接受其他类型的错误,则会将其添加到上面的列表中;所有答案都将在此评分。听起来您将在人们开始工作或提交解决方案之后改变挑战,我认为这不是一个好主意。
霍华德

@霍华德:注意到。规则会根据特定的不当使用准则进行更新;和突变方面。错误已删除。
Williham Totland 2014年

1
我将给出答案。.但我认为这两个句子是“每次转换应引入较小的错误率;每处理1万到1百万个单位大约要有一个错误。” 以及“如果出现以下情况,则该参赛作品将被取消参赛资格:平均每万个单位产生一个以上的错误。” 不兼容。“每次转换都应引入较小的错误率;每处理1万到1百万个单位要产生一个错误的数量级。” 并且“如果符合以下条件,则该参赛作品将被取消参赛资格:平均每百万个单位中产生的错误少于一个。”
Mattsteel

1
我投票结束这个问题是不合时宜的,因为在这个网站上,不熟练的挑战已不再是正题。meta.codegolf.stackexchange.com/a/8326/20469
cat,

Answers:


3

Perl 5.10

我很高兴在Perl中介绍我的解决方案。

当我开始挑战时,我非常确定Perl会保持在1小时以下的极限之下。

为了进行测试,我开发了一个普通样本生成器和一个编码样本生成器。

然后,我开发了编码器,该编码器花费了更多的精力并产生了更长的代码。编码器的工作方式如下:

  1. 第一个循环读取整个文件并将数据拆分为所有三元组的数组
  2. 第二个循环遍历数组,并在每个元素的前面加上其长度
  3. 第三循环再次遍历并映射每个字符以提供输出。

编码后的二进制输出的格式设置为20个八位位组的换行终止“行”,其中每个八位位组编码一个三元组,并带有两个前缀字符(如循环行号)。

例如,最短的三字节输入:

AAA

应该给出三个字节加换行符的最短输出。

00ÿ

那是

30 30 FF 0A

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

应该给出以下二进制。

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

应该由于错误率较小:对于最小的输入,脚本引入了1个错误。

对于300万个三元组文件运行,编码器会引入11个错误。

如果脚本是dnacodec3.pl,则照常在命令提示符处调用运行:

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

解码器的工作原理如下:

  1. first循环读取整个文件,并将数据拆分为具有所有八位字节的数组。它跟踪每个换行符。
  2. 第二个循环检查每个八位位组,保留那些不是以00开头的字节,而忽略其余的字节。普通的Ascii输出格式设置为三行的换行终止行,该行由一个空格分隔。新行与输入中的位置相同

我准备了一个300万个三胞胎样本测试文件(大约12MByte)并测试了计时。将我的笔记本电脑与2.6 GHz的Intel Core i5 vPro配合使用,3M编码器的运行时间通常不到20秒。在运行期间,它需要200-220 MB的RAM。真是浪费!

解码运行时间不到10秒。目前无法引入错误。

再次,用于解码运行

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

这是代码

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

这是示例生成器:

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' ;

我忘了显示错误的注入位置:第二个循环中的push @buf可以解决问题。
Mattsteel 2015年

这很微妙,我给你。在不止一个竞争对手之前,我不会进行全面测试,但这是好东西。:)
Williham Totland 2015年

谢谢。我知道这是对其他朋友的建议...我想使用(仍未使用的)时间功能来改善错误位置的随机性:以奇数或偶数秒开始运行应该会产生不同的输出
Mattsteel 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.