尝试理解这个Perl脚本

3

看起来非常简单,我已经理解了大部分。但由于Perl语法宽松,对于新手来说难以马上融入 :)

my @unique = ();
my %seen   = ();
foreach my $elem ( @array ) {
    next if $seen{ $elem }++;
    push @unique, $elem;
}

这是来自perldoc网站的正确提示。如果我理解正确的话,它也可以写成:

my @unique = ();
my %seen   = ();
my $elem;
foreach $elem ( @array ) {
    if ( $seen{ $elem }++ ) {
        next;
    }
    push ( @unique, $elem );
}

那么我现在的理解是:

  • 声明一个名为unique的数组
  • 声明一个名为seen的哈希表
  • 声明一个名为elem的变量
  • 遍历@array,每次迭代存储在$elem中
  • 如果$elem是哈希表%seen中的键(我不知道++是什么意思),则跳到下一次迭代
  • 将$elem附加到@unique的末尾

我还缺少两件事:

  • 什么时候会将任何内容存储在%seen中?
  • ++是什么意思(在其他语言中它表示递增,但我不知道它是如何工作的)

我知道问题出在这里:

$seen{ $elem }++

我怀疑那一行代码在同时执行多项操作。是否有更简洁、更易懂的方式来书写它?

感谢您的帮助。

4个回答

4

++ 运算符在 Perl 中与其他大多数具有相同功能的语言中执行的操作基本相同:它对变量进行递增。

$seen{ $elem }++;

增加%seen哈希表中与$elem相对应的值。"魔法"在于,如果$seen{$elem}尚未被定义,它会自动创建,并假定其已经存在并且值为0;接着++会将其设置为1。所以这等同于:
if (! exists $seen{$elem}) {
    $seen{$elem} = 0;
}
$seen{$elem} ++;

这被称为“自动引用”。(不,真的,就是这样称呼的。)(编辑2:不,我错了,实际上,“autovification”指的是引用的出现。请参阅perldoc perlref。)
以下是您需要进行翻译的内容:
  • 声明名为@unique的数组变量
  • 声明名为%seen的哈希变量
  • 声明名为$elem的标量变量
  • 遍历@array,每次迭代都存储在$elem中
  • 如果$elem是哈希%seen中的键,则跳过下一次迭代
  • 将$elem的值附加到@unique的末尾
@unique%seen$elem都是变量。标点符号(称为“符号”)表示它们各自是什么类型的变量,并且最好将其视为名称的一部分。

啊,我知道肯定有什么不对劲的地方。在编写代码时经常使用自动赋值是好的实践吗?还是说更好的做法是按照较长的方式编写代码呢? - user623990
2
@MaxMackie:在编写自动初始化代码时没有任何问题——它被编码到Perl中的原因是因为Larry Wall知道它将是一种有用的模式。一旦你意识到了它,它就变得非常合理。在这样的代码中,你只对哈希值进行数值操作,所以把哈希中的值视为从0开始是有意义的。你只检查真实性(非零),而不是定义性,所以这是一个可以接受的假设,它允许你简化你的代码。 - Platinum Azure
3
请注意,自动创建数据结构 (autovivification) 不仅适用于数字,也适用于字符串 (两者均为标量形式)。例如,$hash{$key} ++; 将未定义或不存在的 $hash{$key} 视为0,而 $hash{$key} .= "foo"; 则将其视为空字符串。另外需要注意的是,“未定义的”和“不存在的”这两个概念略有不同;perldoc -f exists 可以了解详情。 - Keith Thompson
3
通常,“自动实体化”一词用于将未定义的左值视为引用,并使其神奇地成为适当类型的引用。这是一个非常不同的情况。 - ysth

3

什么时候会将任何内容存储在%seen中?

当尝试对其进行增量操作时。

++是什么(在其他语言中,它表示递增,但我不明白它是如何工作的)

对未定义的变量进行增量操作使其成为1

它与下面的代码相同:

my @unique = ();
my %seen   = ();
my $elem;
foreach $elem ( @array ) {
    if ( ! $seen{ $elem } ) {
         $seen{ $elem } = 1;  
    } else {
        $seen{ $elem }++;
        push ( @unique, $elem );
    }
}

3
这是Perl中创建由给定数组中“唯一”元素组成的数组的常见模式。
在Perl中,哈希存储与任何给定键相关联的值。如果您没有为给定的键放入任何内容,则会获得undef - 但在数字上下文中,例如当您进行增量操作时,undef 被视为 0,然后递增。 if语句检查真值或假值,正如您所知。在Perl中,0"0"''(空字符串)和undef(以及可能其它一些?)被视为假值。
像在C / C ++ / Java中一样,后置递增返回原始值到包含表达式。因此,这段代码
if ( $seen{ $elem }++ ) {
        next;
    }

对于尚未出现的元素,会返回false(0),并且循环将继续(即next语句不会被执行)。该元素将被放入数组中。但在此之前,增量会发生变化 - 现在哈希表中突然存储了1,这意味着该值已经出现过一次。下次再遇到该值时,循环将被跳过,并且该值不会再次添加到结果数组中。


1
如果%seen中没有具有键$elem的元素,则此行将创建一个新元素(哈希表中的新条目)并具有键$elem。++是一个增量运算符,它会将值为$seen{$elem}的变量加1。由于$seen{$elem}的初始值在数值上为false或零,因此会将$seen{elem}的值增加1。由于++在$seen{$elem}的右侧,因此只有在评估$seen{$elem}之后才会添加它。因此,第一次遇到任何特定的$elem时,此测试将失败,并且它将进入下一步,在唯一元素的列表(数组)中放置$elem。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接