Perl:添加5个直到10个元素的组中的元素,得到2个结果

时间:2013-01-02 10:09:06

标签: arrays perl bioinformatics

我遇到了一个相当独特的问题。我有2个文件正在阅读。这两个文件的小版本如下所示:

File1中

chr1    9873    12227   11873   2354    +   NR_046018   DDX11L1
chr1    760970  763155  762970  2185    +   NR_047520   LOC643837

文件2

chr1    9871    0   
chr1    9872    1
chr1    9873    1
chr1    9874    2
chr1    9875    1
chr1    9876    3
chr1    9877    3
chr1    760970  1
chr1    760971  1
chr1    760972  1
chr1    760973  2
chr1    760974  3
chr1    760975  3
chr1    760976  4
chr1    760977  5
chr1    760978  6
chr1    760979  7
chr1    760980  6
chr1    760981  7
chr1    760982  8
chr1    760983  9
chr1    760984  10
chr1    760985  11
chr1    760986  12
chr1    760987  10
chr1    760988  9
chr1    760989  6

问题

  1. 从第一个文件开始,我必须从每一行中获取第二个元素并将其作为$start。结束位置由$end = $start + 10确定。

  2. 基于$start,我现在必须获取第二个文件,并查看每行的第二个元素。找到$start后,我需要将5个组中第3个元素的下5个对应值相加,直到$end

  3. 因此,当$end$start + 10并且我以5个为一组进行求和时,将获得2个求和值。


    如果第二个文件的第二个元素中不存在$end的某些值,则代码不应该停止,它应该继续执行求和并将总和显示为0(如果是连续组,则为 5个元素不存在)。

    File1 中的文件为例,第2个元素= 9873,分配给$start。因此$end将是$start+10,即9883.

    File2 ,在行的第2个元素中找到$start后,接下来的5行的第3个元素必须总计为1个组,接下来的5个值总计为第2组直到$end

    注意

    这里可以在 File2 中看到,$end,即9883不存在。因此,从9879到9883的值之和必须是zero。它不能总结760970以后的值...

    期望输出

    chr1    9873    12227   11873   2354    +   NR_046018   DDX11L1      10   0
    chr1    760970  763155  762970  2185    +   NR_047520   LOC643837    8   25
    

    注意事项

    1. 在处理实际文件时,$ end = $ start + 10,000(而不是$ end = $ start + 10
    2. 此外,在同一个注释中,将对25个值的组进行求和(而不是5 ),在处理实际文件时获得总计400个值。
    3. 如果$ file2的第二个元素中存在一系列值,则求和应该正常进行,如果不存在连续的25个值,则应打印出0。 / LI>
    4. 文件包含>每个100万行。
    5. 代码

      我到目前为止编写的代码设法执行以下操作:

      1. 从文件中读取。
      2. file1
      3. 分配$start$end
      4. file2 ,将所有第二个元素推送到数组@c_posn;所有第3个元素都放入数组@peak
      5. 检查$start
      6. 中是否有@c_posn

        我无法弄清楚如何进行求和部分。我曾想过创建一个哈希,其中第二个文件的所有第二个元素都进入,第三个元素进入。但哈希是无序的。所以我为第二个元素创建了2个数组@c_posn,为第3个元素创建了@peaks。但是现在我不知道如何同时比较2个数组(以确保760970的值不能求和

        use 5.012;
        use warnings;
        use List::Util qw/first/;
        
        my $file1 = 'chr1trialS.out';
        my $file2 = 'b1.wig';
        
        open my $fh1,'<',$file1 or die qw /Can't_open_file_$file1/;
        open my $fh2,'<',$file2 or die qw /Can't_open_file_$file2/;
        
        my($start, $end);
        while(<$fh1>){
            my @val1 = split;
            $start = $val1[1]; #Assign start value
            $end = $start + 10; #Assign end value
            say $start,"->",$end; #Can be commented out
        }
        
        my @c_posn;
        my @peak;
        
        while(<$fh2>){
            my @val2 = split;   
            push @c_posn,$val2[1]; #Push all 2nd elements 
            push @peak, $val2[2];  #Push all 3rd elements        
        }           
        
        if (first { $_ eq $start} @c_posn) { say "I found it! " } #To check if $start is present in @c_posn
        
        say "@c_posn"; #just to check all 2nd elements are obtained
        say "@peak"; #just to check all 3rd elements are obtained   
        

        感谢您抽出宝贵时间解决我的问题。如果需要任何澄清,请问我。 我将非常感谢任何评论/答案。

3 个答案:

答案 0 :(得分:2)

你对哈希有正确的想法。它是否有序并不是特别相关,因为如果我理解正确,你正在寻找11个特定值(9873,9874,9875 ... 9883),而不是文件中的起始值和下一个10(9873) ,... 9877,760970,... 760975)。

根据您的描述,我会按照以下方式进行操作:

#!/usr/bin/env perl

use strict;
use warnings;

my $sum_interval = 5;   # number of lines to group into each sum
my $sum_count = 2;      # number of sums to generate
my @sums;               # final results of the operation

my %lookup;
open my $fh2, '<', 'file2.txt' or die "Can't open file 2: $!";
while (<$fh2>) { 
  my @data = split;
  $lookup{$data[1]} = $data[2];
}
close $fh2;

open my $fh1, '<', 'file1.txt' or die "Can't open file 1: $!";
while (my $line = <$fh1>) { 
  my @line_sums;
  my $start = (split /\s+/, $line)[1];
  for my $interval_num (0 .. $sum_count - 1) {
    my $cur_sum = 0;
    my $interval_start = $start + ($sum_interval * $interval_num);
    for (0 .. $sum_interval - 1) {
      # use || instead of // for Perl older than 5.10
      $cur_sum += $lookup{$interval_start + $_} // 0;
    }
    push @line_sums, $cur_sum;
  }
  push @sums, \@line_sums;
}
use Data::Dumper; print Dumper(\@sums);

可以改进变量名称,但您可以将$sum_interval$sum_count更改为25和400,它应该在您的实际应用程序中完全相同。

如果您提供的样本数据放入file1.txtfile2.txt,则会生成输出:

$VAR1 = [
          [
            10,
            0
          ],
          [
            8,
            25
          ]
        ];

如果我手工完成总和,此输出与我提出的结果相符。

请注意,我略微偏离了您的规范,因为它从$start汇总到$start + 9而不是$start + 10,因为您说它应该总和为两组五个{{1} } $start是11项。

修改:将初始伪代码修改为完整的可运行程序。

答案 1 :(得分:2)

如果b1.wig足够小,可以读入内存中的哈希,从第2列获取密钥,从第3列中获取值,这很容易。然后,必须完成的只是访问每个键在每个序列中,如果相应的哈希元素不存在则使用零(因此访问它会返回undef)。

您还没有说过如何将新总数与chr1trialS.out中的现有数据分开,所以我使用了空格。当然,如果有必要,这很容易改变。

use strict;
use warnings;

use constant SAMPLE_SIZE => 10;
use constant CHUNK_SIZE => 5;

my $file1 = 'chr1trialS.out';
my $file2 = 'b1.wig';

my %data2;
{
  open my $fh, '<', $file2 or die $!;

  while (<$fh>) {
    my ($key, $val) = (split)[1,2];
    $data2{$key} = $val;
  }
}

open my $fh, '<', $file1 or die $!;

while (<$fh>) {
  chomp;
  my $key = (split)[1];
  my @totals;
  my $n = 0;
  while ($n < SAMPLE_SIZE) {
    push @totals, 0 if $n++ % CHUNK_SIZE == 0;
    $totals[-1] += $data2{$key++} // 0;
  }
  print "$_ @totals\n";
}

<强>输出

chr1    9873    12227   11873   2354    +   NR_046018   DDX11L1 10 0
chr1    760970  763155  762970  2185    +   NR_047520   LOC643837 8 25

答案 2 :(得分:1)

这是我目前的解决方案:

#!/usr/bin/perl

use 5.012; use warnings;

my $file1 = Reader->open("<", "filename1");
my $file2 = Reader->open("<", "filename2");

my $groupsize = 5;
my $step = 10;
my $sum_number = int($step / $groupsize) + ($step % $groupsize ? 1 : 0); # ceil($step/$groupsize)

use constant DEBUG_FLAG => 0;
sub DEBUG (@)   { say STDERR "DEBUG: ", @_ if DEBUG_FLAG }

LINE1:
while (my $line1 = $file1->readline) {
    my (undef, $start) = split ' ', $line1, 3;
    my $end = $start + $step;
    my @sums = (0) x $sum_number; # initialize all fields to zero
    my $i = 0;
    my $last;
    LINE2:
    while (my $line2 = $file2->readline) {
        my (undef, $key, $val) = split ' ', $line2, 4;
        if ($start > $key) { # throw away all keys that are too small
            DEBUG "key $key too small for start $start";
        } elsif ($key >= $end) { # termination condition
            DEBUG "key $key too large for end $end";
            $file2->pushback($line2);
            last LINE2;
        } else {
            $last = $key unless defined $last;
            $i += $key - $last; # get interval. This may be set to "1" as an optimization
            DEBUG "counting ($i): $sums[$i/$groupsize] + $val at $key";
            $sums[$i/$groupsize] += $val;
            $last = $key;
        }
    }
    DEBUG "inner loop broken";
    say join "\t", $line1, @sums; # assuming tab-seperated output
}

{
    package Reader;
    # There is probably a CPAN module for this ... :/
    use Carp;
    use constant DEBUG_FLAG => 0;
    sub open :method {
        my ($class, $mode, $filename) = @_;
        open my $fh, $mode, $filename or die qq(Can't open "$filename": $!);
        bless [$fh, []] => $class;
    }
    sub readline :method {
        my $self = shift;
        return shift @{ $self->[1] } if @{ $self->[1] };
        my $line = scalar readline $self->[0];
        chomp $line if defined $line;
        carp "readline: " . ($line // "undef") if DEBUG_FLAG;
        return $line;
    }
    sub pushback {
        my ($self, $line) = @_;
        carp "pushback: " . ($line // "undef") if DEBUG_FLAG;
        unshift @{ $self->[1] }, $line;
        return $self;
    }
    sub eof :method {
        my $self = shift;
        eof $self->[0];
    }
}

输出:

chr1    9873    12227   11873   2354    +   NR_046018   DDX11L1         10      0
chr1    760970  763155  762970  2185    +   NR_047520   LOC643837       8       25

此解决方案假定两个输入文件按第二个字段按升序排序,并且不会请求重叠序列。如果满足这些条件,它将在恒定的存储器和线性时间内执行。如果没有,它将产生垃圾,你可能有兴趣使用另一个答案(线性记忆,线性时间,没有限制)。事实上,Dave Sherohman的答案一般不那么脆弱,并且在大多数输入上可能表现得更快。

根据您的系统,如果丢弃所有对象方向,可能会提高速度,并且内联缓冲线的代码(或者更确切地说是一条线)。

关于$i = $key - $last:如果跳过密钥,代码将继续有效,并且仍会将数字添加到右侧存储桶中。如果您可以声明不会跳过任何键,或者正确的总和无关紧要(前五行ID小于$end,则不应添加下五个ID),然后删除{{1}变量,简单地将$last递增一个就可以了。