perl делать что-то один раз в цикле while

Я часто работаю с данными биологической последовательности (FASTA), которые имеют следующий формат, где начальная левая угловая скобка используется в качестве разделителя для обозначения нового заголовка последовательности. Эти файлы часто имеют перенос текста (кроме заголовков):

>header1
ACTGACTGACTGACTG
ACTGACTGACTGACTG
>header2
CTGGGACTAGGGGGAG
CTGGGACTAGGGGGAG

Часто я хочу избежать чтения всего файла в память, потому что он может занимать много МБ (иногда ГБ), поэтому я стараюсь сосредоточиться на циклах 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;
    }
}

Раньше у меня были другие проблемы, когда мне нужно было что-то сделать с последней строкой. Например, у меня был скрипт, который читал фасту, сохранял каждый заголовок и затем начинал считать длину его последовательности (опять же построчно), и если она попадала в указанный мной диапазон, я сохранял ее в другой файл. Подсчет прервется, если длина превысит максимум, но я не узнаю, была ли она больше минимума, пока не достигну другого заголовка или конца файла. В последнем случае мне пришлось повторить подпрограмму проверки длины ниже цикла 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;
    }
}

Итак, мой основной вопрос: как указать, что я хочу сделать что-то один раз в цикле, не прибегая к счетчикам или повторению кода вне цикла? Спасибо за любую помощь!


person malcolm    schedule 06.07.2013    source источник
comment
Я думаю, что нашел одно решение, позволяющее избежать повторения шага вне цикла while, который по какой-то причине я не мог заставить работать раньше. Используйте eof (конец файла). Пример: if (eof($inputfasta_fh)) {same subroutine}   -  person malcolm    schedule 16.09.2013


Ответы (2)


Вот решения 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. В HOWTO приведены несколько примеров, которые вы можете использовать сразу.

person Chris Charley    schedule 06.07.2013
comment
Спасибо за предложение. Я слышал о BioPerl, но не использовал его, в основном потому, что я новичок, и мой наставник хотел, чтобы я научился решать проблемы самостоятельно. - person malcolm; 08.08.2013
comment
@Malcolm Да, иногда нет другого пути, кроме как выполнить специальную обработку после завершения цикла while. Удалось ли вам решить проблему? - person Chris Charley; 09.08.2013
comment
Пока что я не нашел решения. Но ваш ответ дал хорошую альтернативу. - person malcolm; 23.08.2013
comment
Я думаю, что нашел одно решение с использованием eof. - person malcolm; 16.09.2013

Непонятно, чего именно вы хотите добиться.
Но если вы точно знаете, что особыми случаями являются первая или последняя строка, у вас есть несколько способов справиться с этим:

Специальная первая линия, не требующая регулярной обработки

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 для первого N-1, а затем обрабатываете последнюю строку отдельно)

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

or

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

or

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
}
person ilomambo    schedule 06.07.2013