【问题标题】:Perl:Add elements in groups of 5 uptill 10 elements, giving 2 resultsPerl:以 5 个最多 10 个元素为一组添加元素,得到 2 个结果
【发布时间】:2012-12-16 16:58:27
【问题描述】:

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

文件1

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 个元素的对应值相加,最多为$end

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


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

以这里的文件为例,来自File1,第二个元素=9873,分配给$start。因此$end 将是$start+10 即9883。

File2中,一旦在行的第二个元素中找到$start,接下来的5行的第3个元素必须相加为1组,接下来的5个值相加为第二组直到$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
  4. 每个文件包含 > 100 万行。

代码

到目前为止,我编写的代码能够做到以下几点:

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

我无法弄清楚如何进行求和部分。我曾想过创建一个哈希,其中第二个文件的所有第二个元素进入 keys,第三个元素进入 values。但是哈希是无序的。所以我创建了 2 个数组,即 @c_posn 用于第二个元素,@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   

感谢您花时间解决我的问题。如果需要任何澄清,请务必问我。 我将不胜感激任何评论/回答。

【问题讨论】:

  • 需要澄清:你想总结哪些项目?我的假设是否正确,您想以五个一组的项目相加,$start 包含和 $end 不包含?但是,这会导致760970 的第一个和为 1+1+1+2+3 = 8,而不是 11。要达到 11,我们必须对六个值求和,这有点奇怪: 那么应该如何分配这些组?
  • @amon 你是对的。总和应该是 8。我将更正所需的输出。求和需要 5 个一组。(实际文件需要 25 个一组)

标签: arrays perl bioinformatics


【解决方案1】:

如果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

【讨论】:

  • 先生您好!我们再见面。我一直很欣赏将看似复杂的问题转换为简单、直接的代码的奇妙方式。 :) 话虽如此,我确实需要一些澄清,如果您能抽出更多时间,将不胜感激。 1. 我们为什么要使用use constant pragma?它提供了什么好处,我们不能简单地使用变量来存储 sample_size 和 chunk_size 吗?
  • 2. 请先生详细说明这段漂亮的代码。我已经理解了其中的一部分,但不是全部:( while ($n &lt; SAMPLE_SIZE) { push @totals, 0 if $n++ % CHUNK_SIZE == 0; $totals[-1] += $data2{$key++} // 0; }
  • @Neal On 1.: 常量适用于在脚本执行期间不会更改的命名值。此外,它们是内联的并且可能是恒定折叠的,这可以(通常,但在这种情况下不完全是)导致速度增加。使用常量只是一个好习惯,即使在这里。
  • @Neal On 2.: 内部的 while 循环可以看作是一个类似于 for my $n (0 .. SAMPLE_SIZE - 1){...} 的 for 循环。只要$nCHUNK_SIZE 的倍数,@totals 中的一个新字段就会被初始化为零。 $totals[-1] 总是访问最后一个字段。如果该特定键有任何数据可用,则将其添加到最后一个字段,否则添加零。然后,$key 递增。
  • @Neal On 3.: split 接受 0-3 个参数。如果没有给出参数,它会在默认变量$_ 中的任何空白处拆分,并尽可能分成几部分。如果给出第一个参数,这是一个正则表达式,确定什么是分隔符。包含一个空格" " 的字符串是特殊的,相当于/\s+/。如果给出第二个参数,则这是您要拆分的字符串。 (第三个参数可以限制您生产的件数)。所以split 表示split " ", $_,但split $_ 表示split /$_/, $_,它将$_ 视为正则表达式,然后匹配自身
【解决方案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,因为您说它应该为两组五个和 $start$start + 10 的总和是 11 项.

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

【讨论】:

  • 你好戴夫!非常感谢您的回答。你是对的,我正在寻找特定的值(9873..9883)。我还把 file2 的数据放入了一个哈希中。但是,我无法让伪代码工作。我复制了完全相同的代码,为简洁起见将$file1_handle 更改为$fh1。当我运行代码时,我收到类似Use of unintialized value $_ in split at program.pl line 47,&lt;$fh1&gt; line 1 ;Use of unintialized value $start in addition at program.pl line 50,&lt;$fh1&gt; line 1;Use of unintialized value $start in addition at program.pl line 50,&lt;$fh1&gt; line 1 的错误警告。
  • 这些警告重复了两次,在第二组警告中line 1 更改为line 2
  • 就像我说的,它是伪代码,不是实际的、经过测试的工作代码,所以我对出现警告并不感到惊讶。甚至是一个实际的错误...split 行应该将$start 设置为(split /\s+/, $line)[1]; - 我忘记了在拆分$_ 以外的值时,您必须明确包含要拆分的模式。 (已编辑答案以更正此问题。)
  • 感谢 Dave 抽出宝贵时间回答我的问题。有趣的是,你和鲍罗丁都遵循大致相同的想法:)
【解决方案3】:

这是我目前的解决方案:

#!/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),则删除 $last 变量并简单地将$i 加一即可。

【讨论】:

  • 无论您使用数组还是散列,您都应该绝对将file2读入内存,除非它太大而无法容纳。来回擦洗磁盘上的文件会很慢。
  • 我相信您还假设 file1 中每一行的范围总和是不重叠的(即,您不会同时执行 9871 和 9874)。如果该假设和关于在第二个字段中对两个文件进行排序的所述假设成立,那么我撤回我之前的评论,因为该文件只会被读入一次(在每个分组的末尾模推回一行),所以相对于预先阅读整个内容的性能影响将最小到零。
  • @DaveSherohman 关于重叠序列,您有一个很好的观点。相比之下,我的代码确实有点脆弱。
  • @amon 感谢您深思熟虑的回答。可惜!我仍然不熟悉 Perl 的面向对象风格 :( 因此,在这个阶段,相当多部分的代码对我来说有点难以理解。我希望你能原谅我的失误,因为我仍然处于新手级别,还有很多内容要覆盖......
猜你喜欢
  • 1970-01-01
  • 2018-01-03
  • 2012-06-22
  • 2020-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-16
  • 1970-01-01
相关资源
最近更新 更多