用于多个范围的循环的Perl

时间:2014-06-24 10:06:55

标签: perl for-loop

在for循环中设置范围计数器的最佳方法是什么?我有一个制表符分隔输入文件,其中前两列很重要。我想找到它们在Pos值范围内出现的分数的最小值和最大值。所以对于示例输入文件:

Pos     Score
1       5
2       17
9       80
38      22
40      11
7       0
302     19
85      33
12      51
293     1
5       19
61      8
71      15

我需要计算每个范围的最小和最大分数(如果存在)。

1-29 (min=?, max=?)
30-59 (min=?, max=?)
60-89 (min=?, max=?)

预期结果:

1-29 (min=0, max=80)
30-59 (min=11, max=22)
60-89 (min=8, max=33)
290-219 (min=1, max=19)

还有另一个与此相关的线程,但它们只计算具有设定范围的事件。我的尝试是设置for循环:

use List::MoreUtils qw( minmax );
my %inputhash;
my %storehash;

open (FF,$inputfile) || die "Cannot open file $inputfile";
while(<FF>) {
    next if $. < 2; #use to trim off first line if there is a header
    my ($Pos, $Score)  = split;
    $inputhash{$Pos} = $Score;
}


for (my $x=1; $x<1600; $x+29) #set to 1600 for now
{
    my $low = $x;
    my $high = $x+29;
    foreach my $i ($low...$high)
    {
        if (exists $inputhash{$i})
        {
            my $score = $inputhash{$i};
            push (@{$storehash{$high}}, $score);
        }
    }
} 

foreach my $range (sort {$a <=> $b} keys %storehash)
{
    my ($minrange, $maxrange) = minmax @{$storehash{$range}};
    print "$range: $minrange, $maxrange\n";
}

有没有更好的方法来解决这个问题?这个当前的实现给了我一个错误:在void上下文中无用的添加(+)。

4 个答案:

答案 0 :(得分:3)

如果将数据推送到数组中,而不是哈希:

$inputarray[$Pos] = $Score;

您可以在数组切片上使用minmax(在删除任何未定义的值之后):

my ($min, $max) = minmax grep {defined} @inputarray[0..3];

e.g。

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

use List::MoreUtils qw(minmax);
use List::Util qw(min);

my @inputarray;
<DATA>;
while (<DATA>) {
    my ($pos, $score) = split;
    $inputarray[$pos] = $score;
}

for (my $i = 1; $i < @inputarray; $i += 29) {
    my $end = min($i + 29, $#inputarray); # Don't overrun the end of the array.
    my ($min, $max) = minmax grep {defined} @inputarray[$i..$end];
    print "$i-$end (min=$min,max=$max)\n" if defined $min;
}

__DATA__
Pos     Score
1       5
2       17
9       80
38      22
40      11
7       0
302     19
85      33
12      51
293     1
5       19
61      8
71      15

输出:

1-30 (min=0,max=80)
30-59 (min=11,max=22)
59-88 (min=8,max=33)
291-302 (min=1,max=19)

答案 1 :(得分:2)

use strict;
use warnings;

use List::Util qw(max min);

my $step = 30;  # group into 30 item ...
my @bins;       # ... bins

<DATA>;         # skip line
while (<DATA>) {
  my ($p, $s) = split;
  push @{$bins[$p / $step]}, $s; 
}

for (my $i = 0; $i < @bins; $i++) {
    next if not $bins[$i];
    printf("%d, %d  (min %d, max %d)\n", 
        $i * $step, ($i + 1) * $step, 
        min(@{$bins[$i]}), max(@{$bins[$i]}));
}

__DATA__
Pos     Score
1       5
2       17
9       80
38      22
40      11
7       0
302     19
85      33
12      51
293     1
5       19
61      8
71      15

<强>输出

0, 30  (min 0, max 80)
30, 60  (min 11, max 22)
60, 90  (min 8, max 33)
270, 300  (min 1, max 1)
300, 330  (min 19, max 19)

答案 2 :(得分:1)

使用命令行,

perl -ane'
  /\d/ or next;
  $i = int($F[0] /30);
  (!defined or $_ >$F[1]) and $_ = $F[1] for $r[$i]{m};
  (!defined or $_ <$F[1]) and $_ = $F[1] for $r[$i]{M};
  }{
  printf("%d-%d (min=%d, max=%d)\n", $_*30, $_*30+29, $r[$_]{m}, $r[$_]{M})
    for grep $r[$_], 0 .. $#r;
' file

输出

0-29 (min=0, max=80)
30-59 (min=11, max=22)
60-89 (min=8, max=33)
270-299 (min=1, max=1)
300-329 (min=19, max=19)

相当于命令行版本的脚本,

my @r;
while (<>) {
  /\d/ or next;
  my @F = split;
  my $i = int($F[0] /30);
  # min topicalizer, refer to $r[$i]{m} as $_
  for ($r[$i]{m}) {
    $_ = $F[1] if !defined or $_ >$F[1];
  }
  # max topicalizer
  for ($r[$i]{M}) {
    $_ = $F[1] if !defined or $_ <$F[1];
  }
}

for (grep $r[$_], 0 .. $#r) {
  printf("%d-%d (min=%d, max=%d)\n", $_*30, $_*30+29, $r[$_]{m}, $r[$_]{M});
}

答案 3 :(得分:1)

错误消息

Useless use of addition (+) in void context

应该已经提醒您for循环的最后一个句子是$x+29而不是$x += 29。除此之外,您在范围上有简单的边界错误

如果您的范围宽度大小相同,那么最简单的方法是通过简单划分计算每个位置的范围,并为每个范围构建一个得分列表。之后可以确定每个范围中的最小值和最大值

此解决方案使用常量WIDTH来确定每个范围的大小;在这种情况下,它是30

use strict;
use warnings;
use autodie;

use List::MoreUtils 'minmax';
use constant WIDTH => 30;

<>; # lose the header

my @buckets;
while (<>) {
  my ($pos, $score) = split;
  push @{ $buckets[$pos / WIDTH] }, $score;
}

for my $i (0 .. $#buckets) {
  next unless my $contents = $buckets[$i];
  my $start = $i * WIDTH;
  printf "%d-%d (min=%d, max=%d)\n",
      $start, $start + WIDTH - 1,
      minmax @$contents;
}

<强>输出

0-29 (min=0, max=80)
30-59 (min=11, max=22)
60-89 (min=8, max=33)
270-299 (min=1, max=1)
300-329 (min=19, max=19)