在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上下文中无用的添加(+)。
答案 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)