私が正しく理解していれば、呼び出しif (exists $ref->{A}->{B}->{$key}) { ... }
が存在するようになります。$ref->{A}
$ref->{A}->{B}
if
これは非常に望ましくないようです。では、「深い」ハッシュ キーが存在するかどうかを確認するにはどうすればよいでしょうか。
私が正しく理解していれば、呼び出しif (exists $ref->{A}->{B}->{$key}) { ... }
が存在するようになります。$ref->{A}
$ref->{A}->{B}
if
これは非常に望ましくないようです。では、「深い」ハッシュ キーが存在するかどうかを確認するにはどうすればよいでしょうか。
autovivificationモジュールのようなものを使用してその機能をオフにするか、Data::Diverを使用する方がはるかに優れています。ただし、これは、プログラマーが自分で行う方法を知っていると私が期待する単純なタスクの 1 つです。ここでこの手法を使用しない場合でも、他の問題について知っておく必要があります。これは基本的にData::Diver
、インターフェイスを取り除いた後の処理です。
これは、データ構造をたどるコツをつかめば簡単です (それを行うモジュールを使用したくない場合)。この例では、check_hash
チェックするキーのハッシュ参照と配列参照を受け取るサブルーチンを作成します。一度に 1 つのレベルをチェックします。キーがそこにない場合、何も返しません。キーがそこにある場合、パスのその部分だけにハッシュをプルーニングし、次のキーで再試行します。トリックは、$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
。選択したパスの値が true であるか、文字列であるか、別のハッシュ参照であるかなどを確認したい場合があります。パスが存在することを確認したら、正しいチェックを提供するだけです。この例では、中断した値をチェックするサブルーチン参照を渡します。好きなものをチェックできます:
#!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
自動生存プラグマを使用して、参照の自動作成を非アクティブ化できます。
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} ) {