未在 foreach 中分配的 Perl 变量:范围问题

Perl variable not assigned in foreach: scope issues

我正在尝试将 .txt 文件中的一些分数归一化,方法是将每个可能的分数(例如 take#v#2;在我的代码中称为 $tokpossense)除以所有分数的总和一个词类型(例如 take#v;称为 $tokpos)。困难在于在处理的每一行时将词类型分组在一起,以便在找到新的词类型/$tokpos 时打印归一化分数。我使用了两个哈希和一个 if 块来实现这一点。

目前,问题似乎是 $tokpos 在第 20 行的 SumHash{$tokpos} 中未定义为键,导致被零除。但是,我相信 $tokpos 在此块的范围内得到了正确定义。到底是什么问题,我将如何最好地解决它?我也很乐意听到解决此问题的替代方法。

这是一个示例输入文件:

i#CL take#v#17 my#CL checks#n#1 to#CL the#CL bank#n#2 .#IT 
Context: i#CL <target>take#v</target> my#CL checks#n to#CL the#CL bank#n
  Scores for take#v
    take#v#1: 17
    take#v#10: 158
    take#v#17: 174
  Winning score: 174
Context: i#CL take#v my#CL <target>checks#n</target> to#CL the#CL bank#n .#IT
  Scores for checks#n
    check#n#1: 198
    check#n#2: 117
    check#n#3: 42
  Winning score: 198
Context: take#v my#CL checks#n to#CL the#CL <target>bank#n</target> .#IT
  Scores for bank#n
    bank#n#1: 81
    bank#n#2: 202
    bank#n#3: 68
    bank#n#4: 37
  Winning score: 202

我的错误代码:

@files = @ARGV;
foreach $file(@files){
    open(IN, $file);
    @lines=<IN>;
    foreach (@lines){
        chomp;
        #store tokpossense (eg. "take#v#1") and rawscore (eg. 4)
        if (($tokpossense,$rawscore)= /^\s{4}(.+): (\d+)/) {
            #split tokpossense for recombination
            ($tok,$pos,$sensenr)=split(/#/,$tokpossense);
            #tokpos (eg. take#v) will be a unique identifier when calculating normalized score
            $tokpos="$tok\#$pos";
            #block for when new tokpos(word) is found in inputfile
            if (defined($prevtokpos) and
                ($tokpos ne $prevtokpos)) {
                    # normalize hash: THE PROBLEM LIES IN $SumHash{$tokpos} which is returned as zero > WHY?
                    foreach (keys %ScoreHash) {
                        $normscore=$ScoreHash{$_}/$SumHash{$tokpos};
                        #print the results to a file
                        print "$_\t$ScoreHash{$_}\t$normscore\n";
                    }
                    #empty hashes
                    undef %ScoreHash;
                    undef %SumHash;
            }
            #prevtokpos is assigned to tokpos for condition above
            $prevtokpos = $tokpos;
            #store the sum of scores for a tokpos identifier for normalization
            $SumHash{$tokpos}+=$rawscore;
            #store the scores for a tokpossense identifier for normalization
            $ScoreHash{$tokpossense}=$rawscore;
        }
        #skip the irrelevant lines of inputfile
        else {next;}
    }
}

额外信息:我正在使用 Pedersen 的 Wordnet WSD 工具进行词义消歧,该工具使用 Wordnet::Similarity::AllWords。输出文件由这个包生成,找到的分数必须标准化才能在我们的工具集中实现。

您没有为 $tokpos 分配任何内容。赋值是评论的一部分——你的编辑器中的语法高亮应该已经告诉你了。 strict 也会告诉你的。

此外,您可能应该在除法中使用 $prevtokpos$tokpos 是您以前没有遇到过的新值。要获取最后一个标记的输出,您必须在循环外处理它,因为没有 $tokpos 来替换它。为避免代码重复,请使用子例程来执行此操作:

#!/usr/bin/perl
use warnings;
use strict;

my %SumHash;
my %ScoreHash;

sub output {
    my $token = shift;
    for (keys %ScoreHash) {
        my $normscore = $ScoreHash{$_} / $SumHash{$token};
        print "$_\t$ScoreHash{$_}\t$normscore\n";
    }
    undef %ScoreHash;
    undef %SumHash;
}

my $prevtokpos;
while (<DATA>){
    chomp;
    if (my ($tokpossense,$rawscore) = /^\s{4}(.+): (\d+)/) {
        my ($tok, $pos, $sensenr) = split /#/, $tokpossense;
        my $tokpos = "$tok\#$pos";
        if (defined $prevtokpos && $tokpos ne $prevtokpos) {
            output($prevtokpos);
        }

        $prevtokpos = $tokpos;
        $SumHash{$tokpos} += $rawscore;
        $ScoreHash{$tokpossense} = $rawscore;
    }
}
output($prevtokpos);

__DATA__
i#CL take#v#17 my#CL checks#n#1 to#CL the#CL bank#n#2 .#IT 
Context: i#CL <target>take#v</target> my#CL checks#n to#CL the#CL bank#n
  Scores for take#v
    take#v#1: 17
    take#v#10: 158
    take#v#17: 174
  Winning score: 174
Context: i#CL take#v my#CL <target>checks#n</target> to#CL the#CL bank#n .#IT
  Scores for checks#n
    check#n#1: 198
    check#n#2: 117
    check#n#3: 42
  Winning score: 198
Context: take#v my#CL checks#n to#CL the#CL <target>bank#n</target> .#IT
  Scores for bank#n
    bank#n#1: 81
    bank#n#2: 202
    bank#n#3: 68
    bank#n#4: 37
  Winning score: 202

您试图在 $tokpos 更改后立即打印结果,这让您感到困惑。一方面,$prevtokpos 的值是完整的,但是您试图输出 $tokpos 的数据;而且你永远不会显示 last 数据块,因为你需要更改 $tokpos 来触发输出。

积累给定文件的所有数据然后在到达文件末尾时打印它要容易得多。该程序通过保留三个值来工作 $tokpos$sense$rawscore 数组 @results 中输出的每一行,以及 [=21] 中每个 $tokpos 值的总分=].然后只需将 @results 的内容转储到一个额外的列中,该列将每个值除以相应的总数。

use strict;
use warnings;
use 5.014; # For non-destructive substitution

for my $file ( @ARGV ) {

    open my $fh, '<', $file or die $!;

    my (@results, %totals);

    while ( <$fh> ) {
        chomp;
        next unless my ($tokpos, $sense, $rawscore) = / ^ \s{4} ( [^#]+ \# [^#]+ ) \# (\d+) : \s+ (\d+)  /x;
        push @results, [ $tokpos, $sense, $rawscore ];
        $totals{$tokpos} += $rawscore;
    }

    print "** $file **\n";
    for my $item ( @results ) {
        my ($tokpos, $sense, $rawscore) = @$item;
        printf "%s\t%s\t%6.4f\n", $tokpos.$sense, $rawscore, $rawscore / $totals{$tokpos};
    }
    print "\n";
}

输出

** tokpos.txt **
take#v#1  17  0.0487
take#v#10 158 0.4527
take#v#17 174 0.4986
check#n#1 198 0.5546
check#n#2 117 0.3277
check#n#3 42  0.1176
bank#n#1  81  0.2088
bank#n#2  202 0.5206
bank#n#3  68  0.1753
bank#n#4  37  0.0954