在这里,我得到了一个包含一些主题结果的文本文件。 如下在Perl中做计数表的可能方法是什么?
Math Peter pass
English Peter pass
Music Peter fail
Science Peter fail
Art Mary fail
Music Mary fail
English Mary fail
Math Bob pass
English Bob fail
Art Bob pass
Music Bob fail
English Mike pass
Science Mike pass
name pass fail
Peter 2 2
Mary 0 3
Bob 2 2
Mike 2 0
我已经尝试过了,可以成功以喷出的格式打印转储
#!/usr/bin/perl
use strict;
use Data::Dumper;
my $CurrentPath = "/tmp";
open(FILE, "/tmp/result.txt") or die("Cannot open file result.txt for reading: $!");
my @results = <FILE>;
s{^\s+|\s+$}{}g foreach @results;
close FILE;
my @data_split = ();
foreach my $result ( @results ) {
push @data_split, [ split /\s+/, $result ];
}
print Dumper \@data_split;
1;
$VAR1 = [
[
'Math',
'Peter',
'pass'
],
[
'Eng',
'Peter',
'pass'
],
...............
答案 0 :(得分:2)
要计数和管理一系列项目,对于任何结构化信息,散列都是非常有用的。使用它们和数组,您可以通过引用来构建可以编码相当复杂的关系的数据结构。参见perldsc。一旦这些变得笨拙,下一步就是编写一个类。
它可以遵循这些原则
use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);
my $file = shift @ARGV;
die "Usage: $0 filename\n" if not $file;
open my $fh, '<', $file or die "Can't open $file: $!";
my %results;
while (<$fh>) {
next if not /\S/; # skip empty lines
my ($subj, $name, $grade) = split;
if (not $subj or not $name or not defined $grade) {
warn "Incomplete data, line: $_";
next;
}
if ($grade eq 'pass') {
$results{$name}->{pass}++;
}
elsif ($grade eq 'fail') {
$results{$name}->{fail}++;
}
else {
warn "Unknown grade format for $name in $subj: $grade";
next;
}
}
dd \%results;
没有单独出现特定分数的名称将保留该分数的hashref。如果您需要这些条目,请后处理%results
以添加它们,例如
foreach my $name (keys %results) {
$results{$name}->{pass} = 0 if not exists $results{$name}->{pass};
$results{$name}->{fail} = 0 if not exists $results{$name}->{fail};
}
或者,添加一条语句以初始化代码中每个记录(名称)的两个分数。
请注意,随着需要的出现,我们可以扩大数据结构,以存储更多信息(例如,$subj
),并且只需很小的代码更改即可。这是使用哈希的另一个好处。
对发布的代码的一些评论
为什么一开始没有use warnings;
?你必须有那个;它直接 有用
使用词汇文件句柄,open my $fh, '<', $file ...
代替全局(FILE
)
一次处理一行,除非出于特定原因 先读取整个内容
您总是想根据您的期望检查输入内容;究竟是什么取决于您的问题和设计。在上面的代码中,所有字段都必须定义且合理(允许等级为0),而实际上您可能接受不存在的等级;适当调整。
当然,如果该程序将要读取的所有文件都与您显示的一样,并且始终具有所有字段并且只有通过/失败等级,那么就无需检查输入
split中的模式/\s+/
应该几乎总是用' '
替换,这是相同的,但是它也会丢弃前导空格。它也是默认值,以及字符串的$_
,因此仅位于上面的split;
(由' '
分割的字符串为$_
)
您还可以使用所获得的内容来构建像此处使用的数据结构。但是我看不到这里的数组的好处
相应计数器的增量可以更简洁地写入
while (<$fh>) {
next if not /\S/;
my ($subj, $name, $grade) = split;
# check input ...
if ($grade !~ /^(?:pass|fail)$/) {
warn "Unknown grade format for $name in $subj: $grade";
next;
}
$results{$name}->{$grade}++;
}
如果您希望您的代码在第三个字段中静静地接受任何内容,并将其计数存储在%results
中,然后取消对pass|fail
的支票。
答案 1 :(得分:-1)
这是一个非常基本的解决方案,它不检查输入数据。它将每个哈希值初始化为{ pass => 0, fail => 0 }
,这样就不需要默认的“缺失”值
请注意,Perl哈希是 unordered ,因此输出的顺序也是不确定的。如果您需要任何特定的东西,那么必须这样说
use strict;
use warnings 'all';
use feature 'say';
open my $fh, '<', 'results.txt' or die $!;
my %grades;
while ( <$fh> ) {
my ($class, $name, $grade) = split;
$grades{$name} //= { pass => 0, fail => 0 };
++$grades{$name}{$grade}
}
say "name\tpass\tfail";
for ( keys %grades ) {
say join "\t", $_, @{ $grades{$_} }{qw/ pass fail /};
}
name pass fail
Mary 0 3
Bob 2 2
Mike 2 0
Peter 2 2