计算和操作文本文件中的出现次数(Perl)

时间:2014-03-31 14:42:50

标签: arrays perl list hash

我有一个标签分隔的文本文件,就像

1J  L  0.5
1J  P  0.4
1J  K  0.2
1J  L  0.3
1B  K  0.7
1B  L  0.2
1B  P  0.3
1B  L  0.6
1B  L  0.3

我想操纵它以获取以下信息:

对于第1列中的每个元素,计算第二列中有多少重复元素,并对第二列中每个元素的第三列中的所有数字的平均值进行计算。所需的输出可以是另一个制表符分隔的文本文件,其中"平均值"是第二列中该元素的平均数:

1st  K#  Average  L#  Average  P# Average 
1J  1  0.2  2  0.4  1  0.4
1B  1  0.7  3  0.38  1  0.3

我该怎么办?我想过用key = 1st专栏做一个Hash of Arrays,但我不认为这样做太有利了。

我还想过创建多个名为@L@P@K的数组,以计算第一列中每个元素的每个元素的出现次数;和其他数组@Ln@Pn@Kn将获得每个数组的所有数字。最后,每个数字除以scalar @L的总和将给出平均数。

但我的主要问题是:如何对第1列的每个元素进行所有这些处理?

编辑:另一种可能性(我现在正在尝试)是创建第一列的所有唯一元素的数组。然后,grep每一个并进行处理。但是可能有更简单的方法吗?

Edit2:第一列中某些元素可能不存在第二列的某些元素 - 问题:除以0.例如:

1J  L  0.5
1J  P  0.4
1J  K  0.2
1J  L  0.3
1B  K  0.7
1B  L  0.2
1B  L  0.3  <- note that this is not P as in the example above.  
1B  L  0.6
1B  L  0.3

4 个答案:

答案 0 :(得分:3)

这是一种方法:

my $result;
while(<DATA>){
    chomp;
    my @data = split;
    $result->{$data[0]}{$data[1]}{sum} += $data[2];
    $result->{$data[0]}{$data[1]}{nbr}++;
}
say "1st\tK#\tavg\tL#\tavg\tP#\tavg";
foreach my $k(keys %$result) {
    print "$k\t";
    for my $c (qw(K L P)) {
        if (exists($result->{$k}{$c}{nbr}) && $result->{$k}{$c}{nbr} != 0) {
            printf("%d\t%.2f\t",$result->{$k}{$c}{nbr},$result->{$k}{$c}{sum}/$result->{$k}{$c}{nbr});
        } else {
            printf("%d\t%.2f\t",0,0);
        }
    }
    print "\n";
}

__DATA__
1J  L  0.5
1J  P  0.4
1J  K  0.2
1J  L  0.3
1B  K  0.7
1B  L  0.2
1B  P  0.3
1B  L  0.6
1B  L  0.3

<强>输出:

1st K#  avg   L#  avg   P#  avg
1B  1   0.70  3   0.37  1   0.30    
1J  1   0.20  2   0.40  1   0.40    

答案 1 :(得分:2)

未经测试的代码:

while (<>) {
    chomp;
    ($x, $y, $z) = split /\t/;
    push @{$f{$x}{$y}}, $z;   # E.g. $f{'1J'}{'L'}[1] will be 0.3
}

@cols = qw/L P K/;
foreach $x (sort keys %f) {
    print "$x\t";
    foreach $y (@cols) {
        $t = $n = 0;
        foreach $z (@{$f{$x}{$y}}) {
            $t += $z;
            ++$n;
        }
        $avg = $n ? $t / $n : 'N/A';
        print "$n\t$avg\t";
    }

    print "\n";
}

答案 2 :(得分:1)

对于count和sum中的每一个,我将使用Hash of Hashes,其中第一列是外部哈希的键,第二列是内部哈希的键。如下所示:

my (%count, %sum);
while(<>) {
    my @F = split / /, $_;

    $count{$F[0]}->{$F[1]}++;
    $sum{$F[0]}->{$F[1]} += $F[2];
}

for my $key (keys %count) {
    print $key;
    for my $subkey ("K", "L", "P") {
        my $average = defined($count{$key}->{$subkey}) ? $sum{$key}->{$subkey} / $count{$key}->{$subkey} : 0;
        ...; # and print the result
    }
    print "\n";
}

答案 3 :(得分:1)

对不起,我这样做了 - 真的 - 但这是一个“单行”(咳咳),我将尝试翻译成一个真实的剧本并解释 - 作为一个练习我自己: - )我希望这个单行解决方案的人为例子可以为其他人提交的更清晰的书面和脚本示例增加一些内容。

perl -anE '$seen{$F[0]}->{$F[1]}++; $sum{$F[0]}->{$F[1]} += $F[2];}{ 
for(keys %seen){say " $_:"; for $F1(sort keys $seen{$_}) { 
say "$F1","s: $seen{$_}->{$F1} avg:",$sum{$_}->{$F1}/$seen{$_}->{$F1}}}' data.txt

有关 Perl的开关的更详细说明,请参阅perlrun(1)。基本上,perl -anE在“autosplit”模式(-a)中启动 Perl 并创建一个while <>循环来读取代码的输入(-n)在' '引号之间执行。 -E打开了所有最新的铃声和口哨声(通常一个使用-e)。这是我尝试解释它的作用。

首先,在while循环中,这种(有点)“oneliner”:

  • 使用空格作为分隔符,将autosplits输入到数组(@F ...对于“字段”来说很糟糕)。
  • 使用%seen{}技巧计算数组中部分匹配行的出现次数。在此处,每次在{%seen的第二列($F[0])中看到一行时,它会增加从@F的第一列($F[1])创建的@F哈希键的值。 1}}重复
  • 使用哈希%sum%total使用$F[2]运算符在第三列(=+)中添加值。有关其他示例,请参阅this perlmonks node

然后,使用“butterflywhile <>突出了使用-n创建的}{循环,END for就像$F1块一样,允许嵌套for循环将所有内容吐出来。我使用@F作为内部printf循环的子项,以提醒自己我从autosplit数组 1B: Ks: 1 avg:0.7 Ls: 3 avg:0.366666666666667 Ps: 1 avg:0.3 1J: Ks: 1 avg:0.2 Ls: 2 avg:0.4 Ps: 1 avg:0.4 的第二列获取它。

输出(我们需要printf才能获得更好的数值结果);

perl -anE '$seen{$F[0]}->{$F[1]}++; $sum{$F[0]}->{$F[1]} += $F[2];}{ 
for(keys %seen){say " $_:"; for $F1(sort keys $seen{$_}) {  
printf("%ss %d avg: %.2f\n", $F1, $seen{$_}->{$F1}, $sum{$_}->{$F1}/$seen{$_}->{$F1})}}' data.txt

这使得数字看起来更好(使用$field[1]格式化)

$field[2]

脚本版本。它递增从数据的第二列($work)中提取的重复键的值;在第二个散列中,它将从第三列(#!/usr/bin/env perl use strict; use warnings; my %seen ; my %sum ; while(<DATA>){ my @fields = split ; $seen{$fields[0]}{$fields[1]}++ ; $sum{$fields[0]}{$fields[1]} += $fields[2]; } for(keys %seen) { print " $_:\n"; for my $f(sort keys $seen{$_}) { printf("%ss %d avg: %.2f\n", $f, $seen{$_}->{$f}, $sum{$_}->{$f}/$seen{$_}->{$f} ); } } __DATA__ 1J L 0.5 1J P 0.4 1J K 0.2 1J L 0.3 1B K 0.7 1B L 0.2 1B L 0.3 1B L 0.6 1B L 0.3 )中提取的键值相加。我想给你留下更实用的风格或者适合工作的CPAN模块,但我必须{{1}}。干杯,一定要问更多 Perl 问题!

{{1}}