【问题标题】:sum up a numeric column with a sliding window defined by values in another column用另一列中的值定义的滑动窗口对数值列求和
【发布时间】:2011-10-01 15:20:42
【问题描述】:

我现在面临一个问题,即用另一列中的值定义的滑动窗口对数值列求和。

(1) 我的数据是制表符分隔的,有两个数字列:

1000 12
2000 10
3000 9
5000 3
9000 5
10000 90
30000 20
31000 32
39000 33
40000 28

(2) 我想将第 2 列与第 1 列定义的窗口相加,窗口大小为(第 1 列 + 3000)。这意味着我需要添加第 3 列(第 3 列 = 总和(第 1 列的行中的第 2 列的所有值到第 1 cloumn+3000))。

看起来像这样:

1000 12 12+10+9
2000 10 10+9+3
3000 9 9
5000 3 3
9000 5 5+90
10000 90 90
30000 20 20+32
31000 32 32
39000 33 33
40000 28

(3) 我是编程新手。我试过 awk,但我失败了。

不知道如何控制第一列的窗口

awk '(i = 1; i

有人可以就这个问题给我任何建议/指导吗?提前致谢。

最好的,

【问题讨论】:

  • 我在您的示例数据中只看到一行。也许您可以重新格式化它,使您的样本数据适合您的问题。使用代码括号强制格式化。
  • DavidO,对不起。我不知道什么是代码括号。我试过但失败了。我的数据中有两列(字段)。
  • 非常感谢您在编辑帖子时提供的帮助,DavidO。
  • 不客气。我仍然不明白你是如何定义你的窗户的。也许您可以添加一个澄清的更新。第一列加 3000 是什么意思?您的第二张表(发布在“看起来像这样:”之后)是您想要的输出吗?它似乎与定义没有任何关系。
  • @DavidO,让F[1] 表示第一列第一行,S[1] 表示第二列第一行。他想计算S[A..B] 的总和,其中F[B+1] 是第一个值大于F[A] + 3000。所以第一行是12+10+9,因为1000 + 3000 = 4000和5000是第一个大于4000的值。

标签: linux perl unix awk


【解决方案1】:

我不太擅长 awk,但这是我在 perl 中编写的一些东西,如果你在 unix 系统上也应该可以运行。假设您将其保存为名为 window.pl 的文件:

#!/usr/bin/perl -w
use strict;

# Usage: window.pl < [filepath or text stream]
# Example: window.pl < window.txt

my $window = 3000;
my @lines = <STDIN>;
my $i = 0;
my $last_line = $#lines;

# Start reading each line
while ($i<= $last_line)
{
    my $current_line = $lines[$i];
    my ($col1, $col2) = ( $current_line =~ /(\d+)\s+(\d+)/ );
    my $ubound = $col1 + $window;
    my @sums = $col2;
    my $lookahead = $i + 1;

    # Start looking at subsequent lines within the window
    while ($lookahead <= $last_line)
    {
        my $next_line = $lines[$lookahead];
        my ($c1, $c2) = ( $next_line =~ /(\d+)\s+(\d+)/ );
        if ($c1 <= $ubound)
        {
            push @sums, $c2;
            ++$lookahead;
        }
        else
        {
            last;
        }
    }

    my $output;
    if ( $#sums > 0 )
    {
        my $sum = join "+", @sums;
        $output = "$col1 $sum\n";
    }
    else
    {
        $output = "$col1 $col2\n";
    }
    print $output;
    ++$i;
}

输出:

1000 12+10+9
2000 10+9+3
3000 9+3
5000 3
9000 5+90
10000 90
30000 20+32
31000 32
39000 33+28
40000 28

这仅在输入文件足够小以读入内存时才有效,但无论如何这也许会对您有所帮助。

祝你好运!

【讨论】:

    【解决方案2】:

    这是一个 Perl 解决方案:

    use warnings;
    use strict;
    
    my (%data, @ids);
    while (<DATA>) { # read in the data
        /^(\d+)\s+(\d+)$/ or die "bad input: $_";
        push @ids, $1;
        $data{$1} = [$2]
    }
    for (0 .. $#ids) { # slide window over data
        my ($i, $id) = ($_ + 1, $ids[$_]);
    
        push @{$data{$id}}, $data{ $ids[$i++] }[0]
            while $i < @ids and $ids[$i] <= $id + 3000;
    }
    
    $" = '+';                                                               #"
    print "$_: @{$data{$_}}\n" for @ids;
    
    __DATA__
    1000 12
    2000 10
    3000 9
    5000 3
    9000 5
    10000 90
    30000 20
    31000 32
    39000 33
    40000 28
    

    哪些打印:

    1000:12+10+9 2000:10+9+3 3000:9+3 5000:3 9000:5+90 10000:90 30000:20+32 31000:32 39000:33+28 40000:28

    【讨论】:

      【解决方案3】:

      这并不是任何一种语言真正擅长的事情,事实上,您所问的是一项相当具有挑战性的编程任务,尤其是对于新手而言。

      不过,这里有一个 awk 脚本供您使用:

      BEGIN {
          window = 3000;
      }
      
      function push(line, sum,   n) {
          n = length(lines);
          lines[n] = line;
          sums[n] = sum;
      }
      
      function pop(  n, i) {
          n = length(lines);
      
          if (n > 1) {
              for(i = 0; i < n - 1; i++) {
                  lines[i] = lines[i + 1];
                  sums[i] = sums[i + 1];
              }
          }
          if (n > 0) {
              delete lines[n - 1];
              delete sums[n - 1];
          }
      }
      
      {
          cur_line = $1;
          value = $2;
          n = length(lines);
          pops = 0;
          for (i = 0; i < n; i++) {
              if (lines[i] + window < cur_line) {
                  print "Sum for " lines[i] " = " sums[i];
                  pops++;
               }
          }
          for (i = 0; i < pops; i++) {
              pop();
          }
          push(cur_line, 0);
          n = length(lines);
          for (i = 0; i < n; i++) {
              sums[i] = sums[i] + value;
          }
      }
      
      END {
          n = length(lines);
          for (i = 0; i < n; i++) {
              if (lines[i] < cur_line + window) {
                  print "Sum for " lines[i] " = " sums[i];
               }
          }
      }
      

      这是对您的示例数据的运行:

      Sum for 1000 = 31
      Sum for 2000 = 22
      Sum for 3000 = 12
      Sum for 5000 = 3
      Sum for 9000 = 95
      Sum for 10000 = 90
      Sum for 30000 = 52
      Sum for 31000 = 32
      Sum for 39000 = 61
      Sum for 40000 = 28
      

      【讨论】:

      • 谢谢威尔。我真的很喜欢 awk 脚本。当我尝试测试它时,我遇到了问题。我将您的脚本保存在文件“slid_awk.awk”中。并使用一行命令: awk '{print $2,$3}' test | awk -f slid_awk.awk。返回的错误是: awk: slid_awk.awk:7: (FILENAME=- FNR=1) fatal: 尝试使用标量 `lines' 作为数组
      • 我会检查你的代码版本。它的意思是,不知何故,变量“lines”被分配了一个正常值(字符串或数字)而不是数组。如果存在将“lines”与“line”变量混淆的拼写错误,则很容易发生这种情况。
      【解决方案4】:

      这是一个稍微紧凑的解决方案版本:

      #!/usr/bin/perl
      use strict;
      use warnings;
      
      use constant WIN_SIZE => 3000;
      
      my @pending;
      
      while (<>) {
          my ($pos, $val) = split;
      
          # Store line info, sum, and when to stop summing
          push @pending, { pos   => $pos,
                           val   => $val,
                           limit => $pos + WIN_SIZE,
                           sum   => 0 };
      
          show($_)   for grep { $_->{limit} <  $pos } @pending; # Show items beyond window
      
          @pending =     grep { $_->{limit} >= $pos } @pending; # Keep items still in window
      
          $_->{sum} += $val for @pending;                       # And continue their sums
      }
      
      # and don't forget those items left within the window when the data ran out
      show($_) for @pending;
      
      sub show {
          my $pending = shift;
          print join("\t", $pending->{pos}, $pending->{val}, $pending->{sum}), "\n";
      }
      

      只需将其放入脚本并在同一行为您提供数据文件,例如:

      $ perl script.pl mydata
      1000    12  31
      2000    10  22
      3000    9   12
      5000    3   3
      9000    5   95
      10000   90  90
      30000   20  52
      31000   32  32
      39000   33  61
      40000   28  28
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        • 2011-01-19
        • 1970-01-01
        相关资源
        最近更新 更多