如何在循环搜索时优化内部?

时间:2014-09-05 11:01:45

标签: perl

我再次需要你的帮助!

将文件标签分隔如下:

chr10.10.2      scaffold1116    94.92   394     13      1       16      409     10474   10860   4.1e-201        697.0
chr10.10.2      scaffold1116    100.00  14      0       0       1       14      10453      10466   1.9e+01 27.0
…………………………

和另一个这样的文件:

chr10.10.1      283
chr10.10.2      409
chr10.10.3      572
chr10.10.4      248
chr10.10.5      143
…………………………

我想根据第二个文件的编号保留第一个文件中的一些特定行。

例如,如果我必须使用" chr10.10.2"保留该行,我必须检查" chr10.10.2"在第二个文件。 我写了一个脚本,但由于这两个文件非常大,需要花费很多时间。 (对于第一个文件的每一行,它搜索第二个文件的所有行)。 有没有办法以更有效的方式搜索第二个文件?

这是我的代码:

#!/usr/bin/perl
use strict;
use warnings;

my $blat_out = $ARGV[0];
my $sizes    = $ARGV[1];

#Cheking the output of "HCEs Vs Genomes" alignments (blat) based on the sizes of the HCEs....

open my $blat_file, $blat_out or die "Could not open $blat_out: $!";
while ( my $line = <$blat_file> ) {
    chomp $line;
    # while( my $size_line = <$size_file>)  {
    if ( $line =~ m/^chr/ ) {
        my @lines = split( '\t', $line );
        #my @size_lines = split('\t', $size_line);
        my $hce        = $lines[0];
        #print "$hce\n";
        my $scaf       = $lines[1];
        my $persent    = $lines[2];
        my $al_length  = $lines[3];
        my $hce_start  = $lines[6];
        my $hce_end    = $lines[7];
        my $scaf_start = $lines[8];
        my $scaf_end   = $lines[9];
        my $score      = $lines[10];
        open my $size_file, $sizes or die "Could not open $sizes: $!";

        while ( my $size_line = <$size_file> ) {
            chomp $size_line;
            my @size_lines = split( '\t', $size_line );
            my $hce_name   = $size_lines[0];
            my $hce_size   = $size_lines[1];
            #print "$hce_size\n";

            if ( $hce eq $hce_name ) {
                my $al_ratio = $al_length / $hce_size;
                if ( ( $persent >= 98 ) && ( $al_ratio >= 0.9 ) ) {
                    print "$line\n";    #print only the lines that satisfies the previous criteria
                }

            }
        }
        #close $size_file;
    }
}

非常感谢您提前, 瓦西利斯。

3 个答案:

答案 0 :(得分:2)

我建议将 $ size_file 存储在内存中(哈希),这样您就不需要为 $ blat_file 的每一行打开它。那个I / 0很重。

您可以创建自己的脚本来执行此操作,也可以使用 File::Slurp 模块。

加分:您还可以使用Text::CSV_XS模块加快解析速度,使用制表符作为分隔符而不是逗号。

此外,这是不相关的,但是仅供参考,您可以转换这些行:

my $hce        = $lines[0];
my $scaf       = $lines[1];
my $persent    = $lines[2];
my $al_length  = $lines[3];
my $hce_start  = $lines[6];
my $hce_end    = $lines[7];
my $scaf_start = $lines[8];
my $scaf_end   = $lines[9];
my $score      = $lines[10];

成:

my ($hce, $scaf, $persent, $al_length, undef, undef, $hce_start, $hce_end, $scaf_start, $scaf_end, $score) = @lines;

答案 1 :(得分:2)

如何使用存储第二个文件的哈希:

# Build hash of hce_name => hce_size
my %size = do {
    open my $fh, '<', $sizes or die "Could not open $sizes: $!";
    map { chomp; split "\t", $_, 2 } <$fh>;
};

open my $blat_file, '<', $blat_out or die "Could not open $blat_out: $!";
while ( my $line = <$blat_file> ) {
    chomp $line;

    next if $line !~ m/^chr/;

    my @fields     = split "\t", $line;
    my $hce        = $fields[0];
    my $scaf       = $fields[1];
    my $persent    = $fields[2];
    my $al_length  = $fields[3];
    my $hce_start  = $fields[6];
    my $hce_end    = $fields[7];
    my $scaf_start = $fields[8];
    my $scaf_end   = $fields[9];
    my $score      = $fields[10];

    next if !exists $size{$hce};

    my $al_ratio = $al_length / $size{$hce};
    if ( $persent >= 98 && $al_ratio >= 0.9 ) {
        print "$line\n";    #print only the lines that satisfies the previous criteria
    }
}

答案 2 :(得分:1)

如果两个文件都非常大,则不要使用哈希表。使用排序。

首先,根据第一列对两个文件进行排序:

$ sort -k 1,1 first.tsv > first.sorted
$ sort -k 1,1 second.tsv > second.sorted

然后逐行浏览第一个和第二个文件,寻找两者之间的匹配。

当有匹配项时,打印它们 - 否则,遍历第一个或第二个文件,具体取决于字符串比较结果:

#!/usr/bin/perl

use strict;
use warnings;

my $firstFn = "first.sorted";
my $secondFn = "second.sorted";
open my $firstFh, "<", $firstFn or die "could not open first file\n";
open my $secondFh, "<", $secondFn or die "could not open second file\n";
my $firstLine = <$firstFh>;
chomp $firstLine;
my @firstElems = split("\t", $firstLine);
my $firstChr = $firstElems[0];
while (<$secondFh>) {
    chomp;
    my ($secondChr, $secondNum) = split("\t", $_);

    #
    # Test *chr string equality: 
    #
    #  1. If secondChr is less than ("lt") firstChr, then we
    #     retrieve the next secondChr.
    #
    #  2. If secondChr is the same as ("eq") firstChr, then we 
    #     print out the first file's current line and retrieve the 
    #     next line from the first file, then re-test.
    #
    #  3. If secondChr is greater than ("gt") firstChr, then we
    #     retrieve the next line from the first file until there
    #     is a match.
    #

    if ($secondChr lt $firstChr) {
        next;
    }
    while ($secondChr eq $firstChr) {
        print STDOUT "$firstLine\n";
        $firstLine = <$firstFh>;
        chomp $firstLine;
        @firstElems = split("\t", $firstLine);
        $firstChr = $firstElems[0];
    }
    while ($secondChr gt $firstChr) {
        $firstLine = <$firstFh>;
        chomp $firstLine;
        @firstElems = split("\t", $firstLine);
        $firstChr = $firstElems[0];
        while ($secondChr eq $firstChr) {
            print STDOUT "$firstLine\n";
            $firstLine = <$firstFh>;
            chomp $firstLine;
            @firstElems = split("\t", $firstLine);
            $firstChr = $firstElems[0];
        }
    }
}
close $secondFh;
close $firstFh;

这是未经测试的,但我认为它应该有用(或至少说明会让你接近)。

这种方法优于使用哈希表的优点是,您只需要足够的内存来存储两行,每行一个。除非你的线路也很长,否则你的内存开销现在几乎不是问题。如果你有非常大的文件,这可能是一个重要的优势。

缺点是排序两个(大)文件的前期时间成本。但是,如果其中一个文件没有改变,如果你经常在两个文件之间进行查找,一些排序时间可以快速摊销。