我有一个文件,其中包含许多人及其分数的数据。我正在尝试计算每个人的平均分数。
Person 1
Scores (\
"0.06, 0.01, 0.07, 0.07, 0.75", \
"0.05, 0.08, 0.01, 0.09, 0.08", \
"0.10, 0.10, 0.11, 0.12, 0.10", \
"0.18, 0.19, 0.20, 0.20, 0.19", \
"0.31, 0.32, 0.32, 0.33, 0.32");
}
Person 2
Scores (\
"0.06, 0.01, 0.07, 0.07, 0.75", \
"0.05, 0.08, 0.01, 0.09, 0.08", \
"0.10, 0.10, 0.11, 0.12, 0.10", \
"0.18, 0.19, 0.20, 0.20, 0.19", \
"0.31, 0.32, 0.32, 0.33, 0.32");
}
预期产出
Person 1 - (avg value)
Person 2 - (avg value)
我尝试过的:
open($in, “<file.txt>”)
or die;
while(<$in>) {
if (/Person/) {
if (/Scores/../}/) {
$_ =~ s/,//g;
$_ =~ s/\\//g; # removing all unwanted characters to take avg of numbers
$_ =~ s/"//g;
$_ =~ s/values//g;
$_ =~ s/\(//g;
$_ =~ s/\)//g;
$_ =~ s/;//g;
$_ =~ s/}/ /g;
@a1 = split(" ",$_);
}
}
}
在此之后,我无法将值存储在数组中以进行进一步计算。
根据提供的数据结构,我们可以利用
}
作为记录分隔符,它简化了任务。
那么只需从每个块中提取信息并进行简单的计算即可。
use strict;
use warnings;
use feature 'say';
$/ = '}';
while( my $record = <DATA> ) {
next unless $record =~ /Person (\d)/;
my $data;
$data->{person} = $1;
$data->{scores}->@* = $record =~ /(\d\.\d{2})/gsm;
$data->{sum} += $_ for $data->{scores}->@*;
$data->{count} = scalar $data->{scores}->@*;
$data->{average} = $data->{sum}/$data->{count};
say "Person: $data->{person} - ($data->{average})";
}
exit 0;
__DATA__
Person 1
Scores (\
"0.06, 0.01, 0.07, 0.07, 0.75", \
"0.05, 0.08, 0.01, 0.09, 0.08", \
"0.10, 0.10, 0.11, 0.12, 0.10", \
"0.18, 0.19, 0.20, 0.20, 0.19", \
"0.31, 0.32, 0.32, 0.33, 0.32");
}
Person 2
Scores (\
"0.06, 0.01, 0.07, 0.07, 0.75", \
"0.05, 0.08, 0.01, 0.09, 0.08", \
"0.10, 0.10, 0.11, 0.12, 0.20", \
"0.18, 0.19, 0.20, 0.20, 0.19", \
"0.31, 0.32, 0.32, 0.33, 0.32");
}
输出
Person: 1 - (0.1744)
Person: 2 - (0.1784)
代码的根本问题是您一次遍历输入数据一行,但您的代码假设解析代码所需的所有部分都存在于该行中。
例如,这两个语句首先检查当前行上的文字
Person
,然后检查同一行上的文字字符串
Scores
。这永远不会匹配——两个文字字符串位于不同的行
if (/Person/) {
if (/Scores/../}/) {
解决这个问题有很多方法,这里是其中之一。
use strict;
use warnings ;
use List::Util qw(sum);
# read the complete file into $data
my $data ;
{
local $/;
$data = <DATA>;
}
# repeatedly match each Person/Scores section
while ($data =~ /Person\s+(\S+)\s+Scores\s+\((.+?)\)/smg)
{
my $person = $1;
my $scores = $2;
# now split $scores into the individual values - store in @scores
my @scores;
while ($scores =~ /(\d+\.\d+)/smg)
{
push @scores, $1
}
# @scores now holds the individual values.
# Can work out the average from them
my $average = sum(@scores) / scalar @scores;
print "Person $person - $average\n";
}
__DATA__
Person 1
Scores (
"0.06, 0.01, 0.07, 0.07, 0.75",
"0.05, 0.08, 0.01, 0.09, 0.08",
"0.10, 0.10, 0.11, 0.12, 0.10",
"0.18, 0.19, 0.20, 0.20, 0.19",
"0.31, 0.32, 0.32, 0.33, 0.32");
}
Person 2
Scores (
"0.06, 0.01, 0.07, 0.07, 0.75",
"0.05, 0.08, 0.01, 0.09, 0.08",
"0.10, 0.10, 0.11, 0.12, 0.10",
"0.18, 0.19, 0.20, 0.20, 0.19",
"0.31, 0.32, 0.32, 0.33, 0.32");
}
输出为
Person 1 - 0.1744
Person 2 - 0.1744
DATA
文件句柄会针对当前脚本文件自动打开。其文件指针设置为直接位于以 __DATA__
开头的行之后的行。我用它来存储您在 file.txt
中的数据。
例如,假设脚本
test.pl
包含此
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
while (<DATA>)
{
chomp;
say uc $_ ;
}
__DATA__
alpha
beta
gamma
delta
运行该脚本会给出
$ perl /tmp/data.pl
ALPHA
BETA
GAMMA
DELTA
使用
DATA
文件句柄对于创建完全独立的测试脚本非常方便。意味着您不依赖额外的文件。
perl -MList::Util=sum -0777 -wnE'
%p = /Person\s+ (\S+) \s+ Scores \s+ (.+?)(?=Person|$)/gsx;
for (keys %p) {
@n = $p{$_} =~ /[0-9.]+/g;
say "Person $_: ", sum(@n)/@n
}
' person_score.txt
将整个文件读入字符串 (
-0777
switch),然后可以在 $_
中使用。
在该字符串中,匹配
Person
并捕获以下标签,然后匹配 Scores
并使用前瞻捕获其后的所有内容以及下一个 Person
(或字符串末尾);对所有此类事件重复(修饰符 /g
)。
这样的列表,由所有 person-id+string-with-scores 对组成,可以分配给一个哈希,其中连续的字符串(person-id 和 string-with-scores)使得键值对。
一旦我们有了这样的哈希值,就很容易了。对于哈希中的每个条目,从带有分数的字符串中提取数字(分数)并将其分配给一个数组,然后用于获取平均值。
或者作为程序放入文件,确实更好
use warnings;
use strict;
use feature 'say';
use List::Util qw(sum);
my $file_content = do { local $/; <> };
my %p = $file_content =~
/Person\s+ (\S+) \s+ Scores \s+ (.+?)(?=Person|$)/gsx;
for my $id (keys %p) {
my @scores = $p{$id} =~ /[0-9.]+/g;
say "Person $id: ", sum(@scores)/@scores
}
运行方式为
program.pl filename
do { local $/; <> }
的解释
$/
变量是输入记录分隔符;通常是换行符。如果它“未设置”,则所有输入都是“一条记录”(对于该 Perl 程序)。现在,下面的 <> 运算符 读取命令行上给出的名称的文件,一次一行......或者,实际上,一次一个“记录”。
因此,当我们取消设置
$/
并执行 <>
时,整个文件会立即被读取,“slurped”。然后将其分配给一个变量,因为 do 块返回其中语句最后返回的任何内容。
最后,local使得对
$/
的更改(未设置)仅保留在当前块中。否则,整个解释器、程序的所有部分都会改变。
所以这个小“习惯用法”是一种将整个文件作为字符串读取到变量中的方法。
当然还有其他方法,值得一提的一个好方法是使用库
use Path::Tiny; # path()
my $content = path($file)->slurp;
这里我们需要文件名(
$file
),不能使用Perl的“魔法”<>
。 (我们可以使用 @ARGV 来保存命令行参数,因此当文件名是命令行上给出的第一个参数时,可以说 path($ARGV[0])
或 path(shift)
。)
请参阅相关文档的链接。