[英]How can I check if a key exists in a deep Perl hash?
如果我理解正確的話 ,調用if (exists $ref->{A}->{B}->{$key}) { ... }
將會出現$ref->{A}
和$ref->{A}->{B}
即使它們在if
之前不存在!
這似乎非常不受歡迎。 那么我該如何檢查是否存在“深度”哈希鍵?
使用像autofivification模塊這樣的功能來關閉該功能,或使用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
您可以使用autovivification pragma來停用引用的自動創建:
use strict;
use warnings;
no autovivification;
my %foo;
print "yes\n" if exists $foo{bar}{baz}{quux};
print join ', ', keys %foo;
它也是詞法,意味着它只會在你指定的范圍內停用它。
在查看頂層之前檢查每個級別是否exist
。
if (exists $ref->{A} and exists $ref->{A}{B} and exists $ref->{A}{B}{$key}) {
}
如果您覺得煩人,可以隨時查看CPAN 。 例如,有Hash::NoVivify
。
看看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));
非常難看,但是如果$ ref是一個復雜的表達式,你不想在重復的存在測試中使用它:
if ( exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key} ) {
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.