Perl哈希表中是否总是需要包含值?

8
我之前有一个问题,著名的Perl专家、Perl作者和Perl培训师brian d foy给出了以下回复
[如果]你正在寻找每个文件名结尾的固定字符序列。您想知道是否在您感兴趣的序列列表中存在该固定序列。将所有扩展名存储在哈希表中,并在该哈希表中查找:
    my( $extension ) = $filename =~ m/\.([^.]+)$/;
    if( exists $hash{$extension} ) { ... }
您不需要构建正则表达式,也不需要通过几个可能的正则表达式交替来检查您需要检查的每个扩展名。
谢谢您的建议,brian。
现在我想知道像上面这种情况的最佳实践是什么。应该只定义键,这就是我需要实现上述内容所需的全部,还是应该始终定义值?

让我澄清一下。当我说“值”时,我指的是除了未定义值(undef)以外的其他值。 - Dr. Faust
1
你可以编辑你的帖子来澄清这样的事情,而不是希望人们阅读评论。 - Chris Lutz
6个回答

7

一般来说,为每个键设置一个明确定义的值更好。当您不关心数值时,惯用的数值是1。

my %hash = map { $_ => 1 } @array;

以这种方式进行操作可以使使用哈希的代码稍微简单一些,因为您可以将$hash{key}用作布尔值。如果值可能未定义,则需要改用更冗长的exists $hash{key}
话虽如此,在某些情况下,undef的值是可取的。例如:假设您正在解析C头文件以提取预处理器符号。将这些符号存储在名称=值对的哈希中是合理的。
#define FOO 1
#define BAR

在 Perl 中,这将映射到:
my %symbols = ( FOO => 1, BAR => undef);

在 C 语言中,#define 定义了一个 符号 而非一个值。在 Perl 中,"defined" 与 C 中的 "exists" 对应。

仅测试$hash{$key}的问题在于,如果哈希中不存在$key,则您刚刚修改了哈希。由此可能导致的错误是史诗级别的。 - daotoad
2
@daotoad:修改了什么?对于简单的检索,不会发生自动生成(但如果您执行类似$hash{$key}{subkey}的操作,则会发生)。 - Michael Carman

5

如果没有值,就不能创建哈希键。该值可以是未定义的,但它必须存在。否则,你怎么构建哈希表呢?或者你的问题是关于值是否可以是未定义的?在这种情况下,我会说你存储的值(未定义,1,0...)完全取决于你。如果有很多人在使用它,那么你可能希望存储一些真实的值,以防其他人不小心使用if($hash{$extension}) {...}而不是exists。


2
你可以创建一个没有值的哈希键。这就是为什么有那个巧妙的defined()运算符存在的原因。 :) - brian d foy
1
perl -le '@hash{ qw(a b c ) } = (); print keys %hash'Perl -le '@hash { qw(a b c) } =(); 打印键%hash' - brian d foy

3

undef是一个值

当然,这种情况总是取决于你当前所做的事情。但是$foo{bar}就像变量$bar一样,我看不出为什么任何一个都不应该偶尔是undef

PS:这就是为什么exists存在的原因。


1
实际上,undef 表示缺少值。这是在你分配任何值之前得到的结果。 - brian d foy
1
@brian d foy:我能理解你的立场,但我倾向于赞同Manni的观点。未定义的值仍然是一个值,而Perl的未定义通常被定义得非常好。:P - Michael Carman

3
如其他人所说,对于仅包含键而不包含值的哈希集(hashset)来说,习惯用法是将值设为1,这样可以方便地进行存在性测试。然而,使用undef作为值也有一定价值。它将强制用户使用exists进行存在性测试,这会稍微更快一些。当然,即使值为1,您也可以使用exists进行存在性测试,以避免用户忘记使用exists而导致的不可避免的错误。

3
我使用 exists(),因为我将其视为一种集合操作,而不是字典查找。 - brian d foy
确切地说,这只是一种更好的方法(更快、更清晰),但问题在于那些习惯于检查值的表达方式的人。这就是为什么我说使用1作为值,但要使用exists是最安全的。 - Chas. Owens
1
使用exists()函数,仅因其速度更快几乎肯定是不正确的优化方式。它是否更清晰可辩:并非每个人都像Brian那样思维清晰、注意微妙之处。话虽如此,对于精确表达(使用exists()进行测试)和宽容编码(使用1作为值)的组合,我会给一个+1。 - Michael Carman
是的,我的应用程序只是测试键是否存在。所以你的意思是我应该将它们的值设置为undef。 - Dr. Faust
@daotoad 自动创建是一个独立的问题,如果(exists $foo{bar}{baz}) {},即使你使用exists,也会在%foo中自动创建一个bar键。 - Chas. Owens
显示剩余7条评论

2

在哈希表中使用undef作为值比存储1更节省内存。


1

把 '1' 存储在 Set-Hash 中被认为是有害的

我知道使用被认为有害是有害的,但这很糟糕,几乎和不受限制的 goto 使用一样糟糕。

好吧,我在几个评论中已经强调了这一点,但我认为我需要一个完整的回应来证明这个问题。

假设我们有一个守护进程,为销售小部件的商店提供后端库存控制。

my @items = qw(
    widget
    thingy
    whozit
    whatsit
);

my @items_in_stock = qw(
    widget
    thingy
);

my %in_stock;
my @in_stock(@items_in_stock) = (1) x @items_in_stock;  #initialize all keys to 1

sub Process_Request {
    my $request = shift;

    if( $request eq REORDER ) {
        Reorder_Items(\@items, \%in_stock);
    }
    else { 
        Error_Response( ILLEGAL_REQUEST );
    }
}

sub Reorder_Items{
   my $items = shift;
   my $in_stock =  shift;

   # Order items we do not have in-stock.
   for my $item ( @$items ) {
       
       Reorder_Item( $item ) 
           if not exists $in_stock->{$item};
   }

}

这个工具很棒,可以自动保持货物库存,非常好用。现在老板想要自动生成有库存的商品目录,所以我们修改 Process_Request() 并添加生成目录的功能。

sub Process_Request {
    my $request = shift;

    if( $request eq REORDER ) {
        Reorder_Items(\@items, \%in_stock);
    }
    if( $request eq CATALOG ) {
        Build_Catalog(\@items, \%in_stock);
    }
    else { 
        Error_Response( ILLEGAL_REQUEST );
    }
}

sub Build_Catalog {
    my $items = shift;
    my $in_stock = shift;

    my $catalog_response = '';
    foreach my $item ( @$items ) {
        $catalog_response .= Catalog_Item($item)
            if $in_stock->{$item};
    }

    return $catalog_response;
} 

在测试中,Build_Catalog() 运行良好。太好了,我们可以发布应用程序了。

糟糕。由于某种原因,没有任何东西被订购,公司的所有库存都已经耗尽。

Build_Catalog()例程向%in_stock添加键,因此Reorder_Items()现在将所有物品视为有库存,并且永远不会下订单。

使用Hash::Utillock_hash可以帮助防止意外哈希修改。如果在调用Build_Catalog()之前锁定了%in_stock,我们将收到致命错误,并且永远不会发布带有错误的应用程序。

总之,最好测试键的存在而不是集合哈希值的真实性。如果您使用存在作为标志,请不要将值设置为“1”,因为这将掩盖错误并使其更难以跟踪。使用lock_hash可以帮助捕捉这些问题。

如果你必须检查值的真实性,在每种情况下都这样做。


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