【问题标题】:perl: match numeric values to a range in two filesperl:将数值与两个文件中的范围匹配
【发布时间】:2014-07-30 01:17:30
【问题描述】:

我有一个非常大的文件,包含开始和结束位置,但这里有一个 sn-p:

(A)   11897   11976           
(B)   17024   18924         
(C)   25687  25709  

和另一个带有开始和结束位置的文件(也是一个 sn-p):

(i) 3631 5899  
(ii) 11649 13714                                       
(iii) 23146 31227           

我想知道值文件 2 是否包含文件 1 中值的开始和结束位置在其范围内。

我想要的结果文件如下所示:

(ii) 11649 18924 (A) 11897 11976      
(iii) 23145 31277 (C) 25687 25709          

我写了一个perl代码:

open my $firstfile, '<', $ARGV[0] or die "$!";
open my $secondfile, '<', $ARGV[1] or die "$!";

while (<$firstfile>) {
    @col=split /\s+/;
    $start=$col[1];
    $end= $col[2];

    while (<$secondfile>) {
        @seccol=split /\s+/;
        $begin=$seccol[1];
        $finish=$seccol[2];     

        print join ("\t", @col, @seccol), "\n" if ($start>=$begin and $end<=$finish);
    }
}

但我的结果文件只显示了第一个匹配项,而没有显示其他匹配项:

(ii) 11649 18924 (A) 11897 11976 

有什么建议吗?

【问题讨论】:

  • 我不认为这是解决方案,但错字:$begin=$secol[1]; 应该是 $seccol[1]。还有一个机会告诉某人在 Perl 脚本的顶部use strict; use warnings;
  • 没错,但这只是我在这里发布问题时的一个错字。现在编辑了。
  • 就您的文件而言,“非常大”有多大?

标签: perl


【解决方案1】:

因为您使用的是嵌套循环,所以在外循环的第一次迭代之后,第二个文件已被完全使用。您可以创建一个包含第一个文件中的元素的数组,而不是重新读取文件,然后将它们与第二个文件进行比较:

use strict;
use warnings;
use autodie;

open my $firstfile, '<', $ARGV[0];
open my $secondfile, '<', $ARGV[1];

my @range;

while (<$firstfile>) {
    push @range, [ split ];
}

while (<$secondfile>) {
    my @col = split;
    my @matches = grep {
        $$_[1] >= $col[1] && $$_[2] <= $col[2]
    } @range;

    if (@matches > 0) {
        for my $ref (@matches) {
            print join("\t", @$ref, @col), "\n";
        }
    }
}

@range 是对第一个文件中列的引用数组。请注意,您无需为 split 指定任何其他参数,因为它默认在空格上拆分。

在第二个while 循环中,将第二个文件的每一列与@range 数组中引用的每组值进行比较。任何匹配项都存储在@matches 中。如果数组的大小大于 0,则每个匹配项都以您最初指定的格式打印出来。

【讨论】:

    【解决方案2】:

    您每次都需要倒带第二个文件,或者(可能最好,取决于它的大小)将它加载到一个数组中。

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my ($start,$end,$begin,$finish);
    
    open my $firstfile, '<', $ARGV[0] or die "$!";
    open my $secondfile, '<', $ARGV[1] or die "$!";
    
    while (<$firstfile>) {
            my @col=split /\s+/;
            $start=$col[1];
            $end= $col[2];
    
            seek($secondfile,0,0);
            while (<$secondfile>) {
               my @seccol=split /\s+/;
               $begin=$seccol[1];
               $finish=$seccol[2];
               print join ("\t", @col, @seccol), "\n" if ($start>=$begin and $end<=$finish);
            }
    }
    

    【讨论】:

    • 我能问一下为什么你在命令 seek($secondfile) 后面加上 0,0 吗?
    • @user3816990 当然可以。第一个零表示“寻找位置 0”,第二个零表示“寻找绝对位置”而不是“寻找相对于当前位置”。因此,这意味着寻求绝对零 - 即开始。 perldoc.perl.org/functions/seek.html
    【解决方案3】:

    这是一个替代的 perl 单行代码:

    perl -lane '
    BEGIN { 
        $x = pop;
        push @range, map[split], <>; 
        @ARGV = $x
    }  
    for (@range) {
        if ($F[1] <= $_->[1] && $F[2] >= $_->[2]) {
            print join " ", @F, @$_
        }
    }' bigfile secondfile
    (ii) 11649 13714 (A) 11897 11976
    (iii) 23146 31227 (C) 25687 25709
    

    使用命令行选项:

    • -l 从每一行中删除换行符并在打印期间将其放回
    • -a 自动将行拆分为数组 @F
    • -n 创建一个 while(&lt;&gt;){..} 循环来处理每一行
    • -e 执行代码块
    • BEGIN 块中,我们遍历大文件创建数组数组
    • 在主体中,我们检查第二列和第三列是否在范围内,如果是,我们打印整行和整个数组内容。

    【讨论】:

    • 请问@F 数组是为哪个文件创建的?
    • @user3816990 正在使用命令行上的-a : autosplit 选项为第二个文件创建@F 数组。使用split 函数在BEGIN 块中拆分大文件。
    猜你喜欢
    • 2017-06-20
    • 2015-08-16
    • 2017-06-18
    • 2018-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多