使用 perl 将分数与其各自的人相加

问题描述 投票:0回答:3

我有一个文件,其中包含许多人及其分数的数据。我正在尝试计算每个人的平均分数。

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(" ",$_);
        }
    }
}

在此之后,我无法将值存储在数组中以进行进一步计算。

arrays perl matrix file-handling
3个回答
3
投票

根据提供的数据结构,我们可以利用

}
作为记录分隔符,它简化了任务。

那么只需从每个块中提取信息并进行简单的计算即可。

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)

2
投票

代码的根本问题是您一次遍历输入数据一行,但您的代码假设解析代码所需的所有部分都存在于该行中。

例如,这两个语句首先检查当前行上的文字

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
文件句柄会针对当前脚本文件自动打开。其文件指针设置为直接位于以
__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
文件句柄对于创建完全独立的测试脚本非常方便。意味着您不依赖额外的文件。


2
投票
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-idstring-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)
。)

请参阅相关文档的链接。

© www.soinside.com 2019 - 2024. All rights reserved.