如何检查密钥是不是存在于深层 Perl 哈希中?

Posted

技术标签:

【中文标题】如何检查密钥是不是存在于深层 Perl 哈希中?【英文标题】:How can I check if a key exists in a deep Perl hash?如何检查密钥是否存在于深层 Perl 哈希中? 【发布时间】:2011-04-11 15:12:40 【问题描述】:

如果我understand correctly,调用if (exists $ref->A->B->$key) ... 将出现$ref->A$ref->A->B,即使它们在if 之前不存在!

这似乎非常不受欢迎。那么我应该如何检查是否存在“深度”哈希键?

【问题讨论】:

我很惊讶这不在 perlfaq 中,考虑到它比大多数已经在那里的 Q 更 FA。给我几分钟,我会解决的:) 哦,看,它在 perlfaq4 中:How can I check if a key exists in a multilevel hash?。它本质上是该线程的摘要。谢谢 *** :) 链接中的部分被修剪或更改 - 现在的链接是 perldoc.perl.org/…? . 【参考方案1】:

最好使用autovivification 之类的模块来关闭该功能,或者使用Data::Diver。然而,这是我希望程序员知道如何自己完成的简单任务之一。即使您在这里不使用此技术,您也应该知道它用于其他问题。这实质上就是 Data::Diver 剥离其界面后所做的事情。

一旦您掌握了遍历数据结构的技巧(如果您不想使用为您完成此任务的模块),这将很容易。在我的示例中,我创建了一个check_hash 子例程,它接受一个哈希引用和一个要检查的键的数组引用。它一次检查一个级别。如果密钥不存在,则不返回任何内容。如果密钥在那里,它会将散列修剪到路径的那一部分,并使用下一个密钥再次尝试。诀窍是$hash 始终是要检查的树的下一部分。我将exists 放在eval 中,以防下一级不是哈希引用。如果路径末尾的哈希值是某种错误值,则诀窍是不会失败。这是任务的重要部分:

sub check_hash 
   my( $hash, $keys ) = @_;

   return unless @$keys;

   foreach my $key ( @$keys ) 
       return unless eval  exists $hash->$key ;
       $hash = $hash->$key;
       

   return 1;
   

不要被接下来的所有代码吓到。重要的部分只是check_hash 子例程。其他一切都在测试和演示:

#!perl
use strict;
use warnings;
use 5.010;

sub check_hash 
   my( $hash, $keys ) = @_;

   return unless @$keys;

   foreach my $key ( @$keys ) 
       return unless eval  exists $hash->$key ;
       $hash = $hash->$key;
       

   return 1;
   

my %hash = (
   a => 
       b => 
           c => 
               d => 
                   e => 
                       f => 'foo!',
                       ,
                   f => 'foo!',
                   ,
               ,
           f => 'foo!',
           g => 'goo!',
           h => 0,
           ,
       f => [ qw( foo goo moo ) ],
       g => undef,
       ,
   f => sub  'foo!' ,
   );

my @paths = (
   [ qw( a b c d     ) ], # true
   [ qw( a b c d e f ) ], # true
   [ qw( b c d )       ], # false
   [ qw( f b c )       ], # false
   [ qw( a f )         ], # true
   [ qw( a f g )       ], # false
   [ qw( a g )         ], # true
   [ qw( a b h )       ], # false
   [ qw( a )           ], # true
   [ qw( )             ], # false
   );

say Dumper( \%hash ); use Data::Dumper; # just to remember the structure    
foreach my $path ( @paths ) 
   printf "%-12s --> %s\n", 
       join( ".", @$path ),
       check_hash( \%hash, $path ) ? 'true' : 'false';
   

这是输出(减去数据转储):

a.b.c.d      --> true
a.b.c.d.e.f  --> true
b.c.d        --> false
f.b.c        --> false
a.f          --> true
a.f.g        --> false
a.g          --> true
a.b.h        --> true
a            --> true
             --> false

现在,您可能想要进行其他检查,而不是 exists。也许您想检查所选路径上的值是否为真,或者是一个字符串,或者另一个哈希引用,或者其他什么。验证路径存在后,只需提供正确的检查即可。在此示例中,我传递了一个子例程引用,它将检查我留下的值。我可以检查任何我喜欢的东西:

#!perl
use strict;
use warnings;
use 5.010;

sub check_hash 
    my( $hash, $sub, $keys ) = @_;

    return unless @$keys;

    foreach my $key ( @$keys ) 
        return unless eval  exists $hash->$key ;
        $hash = $hash->$key;
        

    return $sub->( $hash );
    

my %hash = (
    a => 
        b => 
            c => 
                d => 
                    e => 
                        f => 'foo!',
                        ,
                    f => 'foo!',
                    ,
                ,
            f => 'foo!',
            g => 'goo!',
            h => 0,
            ,
        f => [ qw( foo goo moo ) ],
        g => undef,
        ,
    f => sub  'foo!' ,
    );

my %subs = (
    hash_ref  => sub    ref $_[0] eq   ref   ,
    array_ref => sub    ref $_[0] eq   ref []  ,
    true      => sub  ! ref $_[0] &&   $_[0]   ,
    false     => sub  ! ref $_[0] && ! $_[0]   ,
    exist     => sub  1 ,
    foo       => sub  $_[0] eq 'foo!' ,
    'undef'   => sub  ! defined $_[0] ,
    );

my @paths = (
    [ exist     => qw( a b c d     ) ], # true
    [ hash_ref  => qw( a b c d     ) ], # true
    [ foo       => qw( a b c d     ) ], # false
    [ foo       => qw( a b c d e f ) ], # true
    [ exist     => qw( b c d )       ], # false
    [ exist     => qw( f b c )       ], # false
    [ array_ref => qw( a f )         ], # true
    [ exist     => qw( a f g )       ], # false
    [ 'undef'   => qw( a g )         ], # true
    [ exist     => qw( a b h )       ], # false
    [ hash_ref  => qw( a )           ], # true
    [ exist     => qw( )             ], # false
    );

say Dumper( \%hash ); use Data::Dumper; # just to remember the structure    
foreach my $path ( @paths ) 
    my $sub_name = shift @$path;
    my $sub = $subs$sub_name;
    printf "%10s --> %-12s --> %s\n", 
        $sub_name, 
        join( ".", @$path ),
        check_hash( \%hash, $sub, $path ) ? 'true' : 'false';
    

及其输出:

     exist --> a.b.c.d      --> true
  hash_ref --> a.b.c.d      --> true
       foo --> a.b.c.d      --> false
       foo --> a.b.c.d.e.f  --> true
     exist --> b.c.d        --> false
     exist --> f.b.c        --> false
 array_ref --> a.f          --> true
     exist --> a.f.g        --> false
     undef --> a.g          --> true
     exist --> a.b.h        --> true
  hash_ref --> a            --> true
     exist -->              --> false

【讨论】:

【参考方案2】:

您可以使用autovivification pragma 禁用自动创建引用:

use strict;
use warnings;
no autovivification;

my %foo;
print "yes\n" if exists $foobarbazquux;

print join ', ', keys %foo;

它也是词法的,这意味着它只会在你指定的范围内停用它。

【讨论】:

Can't locate autovivification.pm in @INC?! @David:自动生存一直存在。这个模块只是让你控制它。 @David:你有自动复活。在安装自动激活之前,您没有“没有自动激活”:)【参考方案3】:

在查看顶层之前检查每个级别的existence。

if (exists $ref->A and exists $ref->AB and exists $ref->AB$key) 

如果您觉得这很烦人,您可以随时关注CPAN。比如有Hash::NoVivify

【讨论】:

@David 不,没有区别。唯一能做任何事情的箭头是第一个。连续的 [] 之间的箭头是不必要的,通常最好把它们去掉。 漂白;使用&&and 仅用于流量控制 @ysth blech 回到你身边。我更喜欢低优先级的运算符。 如果您真的关心优先级,请将内容用括号括起来。 && 超过 and 在逻辑比较中。 and 超过 && 在流量控制中。【参考方案4】:

看看Data::Diver。例如:

use Data::Diver qw(Dive);

my $ref =  A =>  foo => "bar"  ;
my $value1 = Dive($ref, qw(A B), $key);
my $value2 = Dive($ref, qw(A foo));

【讨论】:

【参考方案5】:

很丑,但是如果 $ref 是一个复杂的表达式,你不想在重复存在测试中使用:

if ( exists $ $ $ $ref ||  A ||  B ||  key ) 

【讨论】:

那是可憎的。我只是想看看它。您还创建了多达n - 1(其中n 是散列中的级别数)匿名散列引用,其唯一目的是避免目标散列中的自动激活(您改为在匿名散列引用中自动激活)。我想知道与多次调用理智代码的exist 相比,性能如何。 @Chas。欧文斯:性能可能更差,可能差很多倍,这并不重要,因为它需要的时间很短。 在所有键都存在大约三倍的情况下实际上更好。理智的版本在那之后开始获胜,但它们都可以每秒执行超过一百万次,因此无论哪种方式都没有真正的好处。这是我使用的benchmark。 @Chas。欧文斯:我就是这么说的 :) 但你的理智代码并不能保护 $ref 本身不被自动激活。 这种方法的问题是你必须为每组键和深度重新编码相同的东西。这里没有可重用性。

以上是关于如何检查密钥是不是存在于深层 Perl 哈希中?的主要内容,如果未能解决你的问题,请参考以下文章

Perl LWP::Simple::getstore 如何检查文件是不是存在于目标目录中

如何在 Perl 中使用 grep 检查字符串是不是存在 [重复]

如何检查 NSDictionary 中的值是不是存在于字典数组中

无法在 Perl 循环中访问哈希查找表

使用 PL/pgSQL 检查密钥是不是存在于 JSON 中?

检查密钥是不是存在于 JSON 中,然后插入到页面中