最好使用像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
子例程。其他都是测试和演示:
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 ) ],
[ qw( a b c d e f ) ],
[ qw( b c d ) ],
[ qw( f b c ) ],
[ qw( a f ) ],
[ qw( a f g ) ],
[ qw( a g ) ],
[ qw( a b h ) ],
[ qw( a ) ],
[ qw( ) ],
);
say Dumper( \%hash ); use Data::Dumper;
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
。也许你想检查所选路径上的值是否为true、字符串、另一个哈希引用或其他任何值。只需在验证路径存在后提供正确的检查即可。在这个例子中,我传递了一个子程序引用,将检查我最后使用的值。我可以检查任何我喜欢的东西:
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 ) ],
[ hash_ref => qw( a b c d ) ],
[ foo => qw( a b c d ) ],
[ foo => qw( a b c d e f ) ],
[ exist => qw( b c d ) ],
[ exist => qw( f b c ) ],
[ array_ref => qw( a f ) ],
[ exist => qw( a f g ) ],
[ 'undef' => qw( a g ) ],
[ exist => qw( a b h ) ],
[ hash_ref => qw( a ) ],
[ exist => qw( ) ],
);
say Dumper( \%hash ); use Data::Dumper;
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