如果我understand correctly,调用if (exists $ref->{A}->{B}->{$key}) { ... }
将弹出$ref->{A}
和$ref->{A}->{B}
存在,即使它们在if
之前不存在!如何检查密钥是否存在于深度Perl哈希中?
这似乎是非常不想要的。那么我应该如何检查一个“深”散列键是否存在?
如果我understand correctly,调用if (exists $ref->{A}->{B}->{$key}) { ... }
将弹出$ref->{A}
和$ref->{A}->{B}
存在,即使它们在if
之前不存在!如何检查密钥是否存在于深度Perl哈希中?
这似乎是非常不想要的。那么我应该如何检查一个“深”散列键是否存在?
这是更好的使用有点像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
+1感谢您的精心设计! – 2010-09-14 10:20:49
检查每个级别的exist
ence在查看最高级别之前。
if (exists $ref->{A} and exists $ref->{A}{B} and exists $ref->{A}{B}{$key}) {
}
如果您发现讨厌你总是可以看看上CPAN。例如,有Hash::NoVivify
。
有点肮脏,不是吗? – 2010-09-13 11:56:50
还有,'$ ref - > {A} {B} {C}'和'$ ref - > {A} - > {B} - > {C}'有区别吗? – 2010-09-13 12:04:17
@大卫不,没有区别。唯一的箭头是第一个。连续的“{}”和“[]”之间的箭头是不必要的,通常看起来会更好。 – hobbs 2010-09-13 12:26:20
您可以使用autovivification编译停用自动创建引用:
use strict;
use warnings;
no autovivification;
my %foo;
print "yes\n" if exists $foo{bar}{baz}{quux};
print join ', ', keys %foo;
这也是词汇,这意味着它只会关闭它,你在其指定的范围内
'无法在@ INC'中找到autovivification.pm ?!! – 2010-09-13 12:31:15
该错误意味着您需要从CPAN下载并安装'autovivification'编译指示,就像其他任何模块一样。 – toolic 2010-09-13 12:46:23
所以我有autovivification工作没有autovivification? – 2010-09-13 13:07:40
很丑陋,但如果$ ref是一个复杂的表达式,您不想在重复存在的测试中使用:
if (exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key}) {
这是一种憎恶。我只是试着去看看它。您还正在为避免目标散列中的自动版本(而不是自动匿名散列参考)创建高达'n - 1'(其中'n'是散列中的层数)匿名散列函数。我想知道,与多重调用“存在”的合理代码相比,性能如何。 – 2010-09-13 13:46:39
@Chas。欧文斯:表现可能更糟,也许更糟糕,考虑到它只需要微不足道的时间,这根本不重要。 – ysth 2010-09-13 13:51:29
在这种情况下,所有的钥匙都存在三次左右的情况实际上会更好。在这之后,理智的版本开始获胜,但它们都可以每秒执行一百万次以上,所以这两种方式都没有真正的好处。这是我使用的[benchmark](http://codepad.org/tXIMrpVW)。 – 2010-09-13 14:07:49
请看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));
我很惊讶,这不是在perlfaq,考虑到它更FA比大多数的QS已经在那里。给我几分钟,我会修复:) – 2010-09-14 05:00:34
哦,看,它在perlfaq4:[我如何检查密钥是否存在于多级哈希?](http://faq.perl.org/ perlfaq4.html#How_can_I_check_if_a)。它基本上是这个线程的总结。谢谢StackOverflow :) – 2010-09-17 15:57:17