perl在一段时间内做一些事情

时间:2013-07-06 02:19:56

标签: perl while-loop flow perl5

我经常使用具有以下格式的生物序列数据(FASTA),其中前导左尖括号用作分隔符以指示新的序列标题。这些文件通常包含文本(标题除外):

>header1
ACTGACTGACTGACTG
ACTGACTGACTGACTG
>header2
CTGGGACTAGGGGGAG
CTGGGACTAGGGGGAG

通常,我想避免将整个文件读入内存,因为它可能是很多MB(有时是GB),所以我尝试着重于while循环并逐行阅读。但是,我发现自己经常需要添加额外的代码来在文件的顶部或底部执行一些独特的操作。例如,今天我想删除一些文件的文本包装,这看起来很简单:

while (my $line = <$inputfasta_fh>) {
    chomp($line);
    if ($line =~ /^>/) {
        print $outputfasta_fh "$line\n";
    }
    else {
        print $outputfasta_fh $line;
    }
}

但是,我意识到除了第一个之外的所有标题之前我需要一个换行符(否则它们将连接到前一个序列的末尾)。所以,这是我粗暴的解决方法。

my $switch = 0;
while (my $line = <$inputfasta_fh>) {
    chomp($line);
    if ($line =~ /^>/) {
        if ($switch == 1) {
            print $outputfasta_fh "\n";
        }
        print $outputfasta_fh "$line\n";
        $switch = 1;
    }
    else {
        print $outputfasta_fh $line;
    }
}

以前,我还有其他问题需要在最后一行做些什么。例如,我有一个脚本可以读取一个fasta,存储每个头,然后开始计算它的序列长度(再次逐行),如果它在我指定的范围内,我将它保存到另一个文件。如果长度超过最大值,则计数将中止,但在我到达另一个标题或文件末尾之前,我不知道它是否超过最小值。在后一种情况下,我不得不重复while循环下面的长度检查子程序。我想避免重复最后一部分。

my $length = 0;
my $header;
my @line_array;

while (my $line = <$inputfasta_fh>) {
    chomp($line);
    if ($line =~ /^>/) {
        # check if previous sequence had a length within range
        if (check_length($length, $minlength, $maxlength) == 1) {
            print $outputfasta_fh "$header\n";
            print $outputfasta_fh join ("\n", @line_array), "\n";
        }
        undef @line_array;
        $header = $line;
        $length = 0;
    }
    else {
        if ($length <= $maxlength) { # no point in measuring any more
            push (@linearray, $line);
            $length += length($line);
        }
    }
}

#and now for the last sequence
if (check_length($length, $minlength, $maxlength) == 1) {
    print $outputfasta_fh "$header\n";
    print $outputfasta_fh join ("\n", @line_array), "\n";
}

sub check_length {
    my ($length, $minlength, $maxlength) = @_;
    if (($length >= $minlength) && ($length <= $maxlength)) {
        return 1;
    }
    else {
        return 0;
    }
}

所以,我的基本问题是如何表明我想在循环中做一次而不诉诸计数器或在循环外重复代码?谢谢你的帮助!

2 个答案:

答案 0 :(得分:3)

以下是您所描述的2个问题的解决方案。它们使用BioPerl发行版中的模块解决。在这种情况下,Bio::SeqIO模块打开文件,Bio::Seq模块提供它提供的一些方法(长度,宽度)。您可以看到他们如何简化解决方案!

#!/usr/bin/perl
use strict;
use warnings;
use Bio::SeqIO;

my $in  = Bio::SeqIO->new( -file   => "input1.txt" ,
                           -format => 'fasta');
my $out = Bio::SeqIO->new( -file   => '>test.dat',
                           -format => 'fasta');

while ( my $seq = $in->next_seq() ) {
    $out->width($seq->length); # sequence on 1 line.
    $out->write_seq($seq);
}

my ($minlen, $maxlen) = (40, 1000);

while ( my $seq = $in->next_seq() ){
    my $len = $seq->length;
    out->write_seq($seq) if $minlen <= $len && $len <= $maxlen;
}

查看模块是值得的 - 从这两个示例中可以看出,结果代码更简洁,更容易理解。你可以环顾BioPerl wikiHOWTOs提供了一些可以立即使用的示例。

答案 1 :(得分:1)

目前尚不清楚您想要达到的目标 但是,如果您确定特殊情况是第一行或最后一行,您有几种方法可以处理它:

不需要定期处理的特殊第一行

Process first line
$line = <$INPUT>;
... process line

Regular processing
while(<$INPUT>) {
... process lines
}

特殊的第一行,也需要定期处理

Process first line
$line = <$INPUT>;
... process line

Regular processing
do {
... process lines
} while(<$INPUT>);

特殊的最后一行,

这里你没有办法预先识别最后一行,所以你必须在循环中这样做(除非你确切知道有多少行并且使用for循环用于第一行1然后分别处理最后一行)

while(<$INPUT>) {
   break if islastline();
   ... process lines
}
... process last line

while(<$INPUT>) {
   ... process lines
   break if islastline();
}
... process last line

for($i=0; $i<N-1 ; $i++) {
   $line = <$INPUT>;
   ...process lines
}
$line = <$INPUT>
... process last line

您描述的其他情况,您需要计算的地方以及完成后,循环继续,但您不再需要计数是不同的。如果您担心代码看起来“干净”,只需将循环分成两部分:

内部临时处理

first part does the whole package
while(<$INPUT>) {
   ...regular processing
   ...special processing
   break if specialProcessingDone();
}

second part does not need to do special processing anymore
while(<$INPUT>) {
   ...regular processing
}