【问题标题】:Efficiently get hash entry only if it exists in Perl仅当 Perl 中存在哈希条目时才有效获取哈希条目
【发布时间】:2014-09-16 21:52:18
【问题描述】:

我经常写这样的代码片段:

if (exists $myHash->{$key}) {
    $value = $myHash->{$key};
}

如果哈希中有那个键,我想做的是从哈希中获取值,同时如果哈希条目不存在,我想避免自动激活它。

然而,这让我感到非常低效:我正在做一个哈希查找以找出一个键是否存在,然后如果它确实存在,我正在对同一个键进行另一个哈希查找以提取它。

在多级结构中效率会更低:

if (exists $myHash->{$key1} 
    && exists $myHash->{$key1}{$key2} 
    && exists $myHash->{$key1}{$key2}{$key3}) {

    $value = $myHash->{$key1}{$key2}{$key3};
}

这里我大概做了 9 次哈希查找而不是 3 次!

perl 是否足够聪明来优化这种情况?或者是否有其他一些习惯用法可以在不自动激活条目或进行两次连续查找的情况下获取哈希值?

我知道autovivification 模块,但如果可能的话,我正在寻找不需要安装 XS 模块的解决方案。此外,我还没有机会尝试这个模块,我不完全确定在多级哈希的情况下会发生什么 - pod 说:

$h->{$key}

如果密钥不存在,将返回 undef - 这是否意味着:

$h->{$key1}{$key2}

如果 $key1 不存在会死吗,因为我试图取消引用 undef?如果是这样,为了避免这种情况,大概您仍然需要进行多级测试才能存在。

【问题讨论】:

    标签: perl hashtable exists autovivification


    【解决方案1】:

    我不会担心优化,因为哈希查找速度很快。但是对于您的第一种情况,您可以这样做:

    if (my $v = $hash{$key}) {
        print "have $key => $v\n";
    }
    

    同样:

    if ( ($v = $hash{key1}) && ($v = $v->{key2}) ) { 
        print "Got $v\n";
    }
    

    【讨论】:

    • 这令人惊讶 - 我曾认为任何不存在的散列条目的左值使用都会将键添加到散列中(在这种情况下为 undef 值)。显然我错了。令人惊讶的是,在编写了多年的 perl 代码之后,我没有意识到这一点!
    • 自动激活只发生在嵌套访问中
    • @harmic,是的,如果哈希条目是左值,它会自动激活,但你可能误解了左值的含义。在这种情况下,哈希位于赋值的右侧;这使它成为右值,而不是左值。正如@perreal 所说,右值哈希条目的自动激活只发生在嵌套访问中 - 例如$h->{foo}{bar}{baz} 会自动执行$h->{foo}{bar} //= {} 之类的操作。
    • @tobyink 抱歉,这是一个错字,我的意思是右值
    • @perreal:这样你就不会测试散列元素是否存在,正如OP所要求的那样,你正在测试$hash{$key}是否为true,当然这是一个不同的(更严格的)测试。
    【解决方案2】:

    单级访问不会发生自动激活,因此您可以安全地编写

    my $value = $hash{$key};
    

    对于多级访问,中间条目将被自动激活。例如

    my $value = $hash{a}{b};
    

    如果$hash{a} 尚不存在,将创建对空哈希的引用。 (如果它确实存在并且不是哈希引用,perl 将抛出一个错误并死掉。)为避免这种情况,您需要先检查每个级别。您可以编写一个子程序来检查是否存在任意嵌套的键。

    sub safe_exists {
        my $x = shift;
        foreach my $k (@_) {
            no warnings 'uninitialized';
            return unless ref $x eq ref {};
            return unless exists $x->{$k};
            $x = $x->{$k};
        }
        return 1;
    }
    
    if (safe_exists(\%hash, qw(a b))) {...}
    

    根据您的算法(以及您尝试避免自动激活的原因)锁定您的哈希可能是no autovivification 或多层exists 测试的有用替代方案。

    use Hash::Util;
    
    my %hash = (a => { b => 1 });
    Hash::Util::lock_hash_recurse(%hash);
    
    say $h{a}{b}; # 1
    say $h{a}{c}; # error!
    

    在处理复杂数据结构时,我主要使用它来检测编程错误。它对于检测键入错误的键名或无意修改值很有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-12
      • 2011-08-06
      • 2021-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-22
      • 2012-10-19
      相关资源
      最近更新 更多