检查Perl哈希表键的存在,还是真值更好?

15

当分配给一个仅包含键的哈希表(其中值并不是真正需要的)时,更倾向于使用哪种方式:

$hash{$new_key} = "";

或者说:

$hash{$new_key} = 1;

其中一个必须使用 exists 检查键是否存在,而另一个则允许你使用以下任意一种形式:

if (exists $hash{$some_key})
或者
if ($hash{$some_key})

我认为赋值1可能更好,但这样做会有什么问题吗?这真的很重要吗?


11
你的标题听起来很有哲学意味 :) - JimDaniel
5
好的,至少我没有问“如何在perl中检查真相的存在?” - Joe
大家似乎都在说这并不重要,这也是我所认为的。只是好奇。谢谢大家。你们都会得到赞成票。 - Joe
相关问题:https://dev59.com/ZUfSa4cB1Zd3GeqPBfNl - Sinan Ünür
会起作用的(抱歉,因为浏览器的问题导致文本不连贯)。 - Jim Dennis
显示剩余2条评论
8个回答

16

这取决于您需要键是否存在或具有真实值。测试您需要的东西。如果您仅使用哈希来查看某些内容是否在列表中,则exists()是正确的方法。如果您正在执行其他操作,则检查值可能是正确的方法。


好的,所以两者并没有一个被更加倾向于使用吗?只是好奇。 - Joe
1
首选什么?这取决于你想做什么。 - brian d foy
当我想到这个问题时,我正在检查是否之前见过某些东西。所以如果没有见过,就将其添加到哈希表中。我总是看到这样做的方式是 $hash{$key} = "",但今天早些时候有人用 $hash{$key} = 1 做了同样的事情。我只是在寻找一个好的、惯用的答案。也许我的问题应该更具体一些。 - Joe
我喜欢将"1"赋给变量然后只需检查真值,因为这样可以减少打字的数量 :) - mpeters
我喜欢先赋值为1再检查是否存在,因为这样更加容错性好。如果我不小心将空字符串赋值给它,它仍然能正常工作。如果我错误地使用真值检查而非存在检查,它仍然可以正常工作。只有当我两个错误都犯了,它才会出问题。这是偏向于鲁棒性胜于正确性的情况,这可能与你的需求和个人风格有关。 - Adam Bellaire

11

当不需要这些值时,你经常会看到这种用法:

my %exists;
$exists{$_}++ for @list;

这将导致它被设置为1。


7
它将它设置为键出现次数在@list中的计数。 - brian d foy
在我看来,这是一个不错的习惯用语,尽管如果我没记错的话,早期版本的 Perl(也许是 5.6?)会在使用 use strict; 的情况下产生警告,抱怨你在任何键的第一个增加操作中操纵未定义的变量。 - j_random_hacker
1
我不认为5.6会这样做 - 可能是之前的版本(我的公司一直在广泛使用5.6,直到去年)。 - Chris Simmons
2
在最近的 perlsyn 文档中,它说:“当作为数字使用时,'undef' 被视为 0;[...] 如果启用警告,则每当您将 'undef' 视为字符串或数字时,都会收到未初始化值的通知。好吧,通常是这样。 [...] 对于操作未定义左值(例如:my $a; $a++;)的运算符,如 '++'、'--'、'+='、'-=' 和 '.=',也始终不受此类警告的影响。”不知道这种行为有多久了,但也许有人可以查看旧版本的 perlsyn 文档来找出... - Michael Krebs
或者这个习语:my %exists; @exists{@list} = (); 或者这个习语: my %seen; ... 除非($seen {$item}++),否则 {打印“第一次为$item”} - ysth
显示剩余2条评论

9

如果你想节省内存(通常只有在使用非常大的哈希表时才会关注内存),你可以将值设置为undef并测试其是否存在。Undef被实现为单例,因此成千上万个undef都只是指向同一个值的指针。将每个值设置为空字符串或1将为每个元素分配不同的标量值。

my %exists;
@exists{@list} = ();

考虑到你后来的评论提到了你的预期使用情况,这是我多次看到并使用的习语:

my %seen;
while (<>) {
    next if $seen{$_}++; # false the first time, true every successive time
    ...process line...
}

我喜欢这个。但是 ()undef 是一样的吗? - Joe
2
在此列表上下文中,它完成了与右侧相比左侧有更多元素的相同操作。这里发生的事情与您编写“($foo,$bar) = (1);”时发生的事情相同。$ foo获得1,$ bar获得undef。从概念上讲,Perl使用足够的“ghost” undefs扩展赋值的右侧以填充左侧。话虽如此,我几乎总是使用$exists {$_} ++惯用语 - 只是为了节省一些调试时间。不必记住$exists {$foo}可能存在但未定义,这样会更容易。 - KingPong

3
假设您实际上需要检查键的存在性,但编写了检查真实性的代码。它在程序的各个地方都检查真实性。然后突然出现了一些误解,您实际上应该存储从键到字符串值的映射;这些字符串应在与您已经实现的相同数据流中使用。
并且这些字符串可以为空!
因此,您应该重构程序或创建另一个哈希表,因为真实性检查不再检查存在性。如果您从一开始就检查存在性,这种情况就不会发生。
(编辑了一下,因为不知道为什么会被投票否决。)

3

* 更新: * Sinan 指出,我的谨慎方法对哈希元素的创建已经过时,并且在更新版本的 Perl 中不是问题。我已经编辑了下面的文章,并对此发表了一些新的想法。

仅测试真实性的问题在于,您可以使用我学习的老旧版本的 Perl 修改哈希。这段代码在 Perl 5.8 中是安全的:

my %foo = ();

if( $foo{bar} ) {
   print "never happens";
}

print keys %foo;

这是自动实例化的负面影响之一(总体而言我喜欢自动实例化,但这就是它的弱点)。
在许多情况下,这并不是什么大问题。但需要注意可能会出现潜在问题。我通过锁定任何必须保持不变的哈希来解决这个问题。
实际上,我经常在进行布尔测试之前进行exists测试。
if( exists $foo{bar} and $foo{bar} ) {
    # hash is not modified due to short circuit
}

数组也可能出现数据结构的改变。如果您访问$foo[2000],则数组将被扩展。因此,在意外扩展数组之前测试其是否存在可能是一个好主意。实际上,这比相应的哈希行为要少得多。<-- 讽刺的是,只有在perls 5.6及更高版本中才能在数组上使用exists,这里可能已经修复了这个问题。

如果我需要深入挖掘数据结构,我会使用Data::Diver。它会自动检查结构中每个级别的存在性,以防止意外修改数据结构。

最重要的是在每个脚本/程序中保持一致。遇到问题的最简单方法是在这里测试存在性,但在那里进行真相测试。特别是如果您访问相同的哈希表进行两组测试。

关于自动创建数据结构更新的最终想法: 一系列的研究显示了几个问题。在发布之前我应该测试我的代码,由于没有这样做,我传播了错误信息,对此我深表歉意。我也发现仍然有 一些难以察觉的自动创建数据结构问题,足以让我们需要一个 开放式待办事项来解决。所以,即使它可能是错误的、老式的和愚蠢的,我将继续明确地采取措施来控制自动创建数据结构并将其限制在我想要它发生的时候 只有 发生。顺便说一下,当自动创建数据结构起作用时,它是一件好事。我认为特别针对 if 的情况以防止自动创建数据结构是正确的事情 - 它消除了大量额外的代码需求,但我希望能找到一些详细说明这种行为的文档。

+1,好观点,有时自动初始化并不那么好用。哈希锁定很有趣,但我注意到它仅适用于 Perl >= 5.8。 - j_random_hacker
1
如果($foo {bar})不会自动创建$foo {bar} - 至少在最近的Perl中是这样。 我不知道行为在某个时候是否有所不同。 - Sinan Ünür
4
实际上Sinan是正确的。底层不会自动创建(我认为这一直是这样的);但是多级访问(例如if ($foo{bar}{baz}{qux}))将自动创建除最底层之外的所有级别(也就是说,在这种情况下,$foo{bar}{baz}将被创建)。 - j_random_hacker
非常出色和全面的更新!如果偏执、守旧和愚蠢是错误的话,那我宁愿错下去。 :) - j_random_hacker

2

正如之前的回答所说,这取决于您想要实现什么;如果您只是想从某个集合中获取(例如)唯一值(其元素然后形成键),则可以使用exists(如果在分配值之前首先检查exists,则还可以帮助捕获重复项)。

不知道应用程序的情况下,更具体的描述就比较困难了。



0

我通常检查defined值。这是你忽略的中间情况。不完全是“真实”的,也不完全是“存在”的。(大多数情况下是如此,但并非完全如此。)

现在理论上,更加通用的方式是exists,例如

if ( exists $hash{$key} ) return 'strawberry';

这种情况适用于键存在且值为0,或者键已被分配为undef。键只需要存在即可通过此测试。

然而,我很少发现需要测试键的存在性。

  1. 哈希常常是API的一部分,如果你正在处理它们,你对可以存储的值的范围有一些了解。配置项将寻找特定的内容;作为无序参数键,子程序将寻找特定的内容。

  2. 我认为“无限表”的概念非常灵活。而且exists x <=> defined x 对此很有效。在表中,每个可想象的值都被“设置”,但只有有限数量的键被定义,其余的被视为未定义。

    因此,通常情况下,除非哈希中定义了一个值,否则我不关心它是什么。我认为它是一个假值。在我编写的大多数代码中,存储undef和不存储任何东西是等效的。这进一步激励了下面的项目。

  3. 大多数时候,我需要知道一个键是否在表中,我需要用它来做其他事情。首先,我将值存储在本地,然后测试它是否为已定义的值。

     my $value = $hash{$key};
     if ( defined $value ) { 
         push @valid_values, $value;
     }
    

    如果我能确信在查找exists和查找使用值之间有一些本地常见子表达式优化,那么我就不会这么挑剔了。但我不喜欢从哈希中检索超过一次。所以我1)缓存值并2)每次都检查它。

话虽如此,如果我知道该值不应为0,例如在查找或参数表中,我可以收紧标准。因此,有时我会测试真实性。但我也可以随意收紧任何测试。

     if ( ( $hash{$key} || '' ) =~ m/^(?:Bears|Lions|Packers|Vikings)$/ ) { 
         $nfc_north++;
     }

当然,这里的操作原则是defined适用于“无限”的表。在表中设置了每个可想象的值,但只有有限数量的键被定义。
有一种情况,你可能正在处理一个完全匿名的哈希。但是,那么你对无法通过keysvalues满足的键有什么兴趣呢?即使你正在制作一个通用哈希“方便函数”,最好不要关注特定键的存在,以完全中立的方式对待其他人存储的内容。

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