如何检查密钥是不是存在于深层 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】:
在查看顶层之前检查每个级别的exist
ence。
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 中的值是不是存在于字典数组中