30

が正しく理解していれば、呼び出しif (exists $ref->{A}->{B}->{$key}) { ... }が存在するようになります。$ref->{A}$ref->{A}->{B}if

これは非常に望ましくないようです。では、「深い」ハッシュ キーが存在するかどうかを確認するにはどうすればよいでしょうか。

4

5 に答える 5

40

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
于 2010-09-14T04:32:29.657 に答える
15

自動生存プラグマを使用して、参照の自動作成を非アクティブ化できます。

use strict;
use warnings;
no autovivification;

my %foo;
print "yes\n" if exists $foo{bar}{baz}{quux};

print join ', ', keys %foo;

また、字句です。つまり、指定したスコープ内でのみ非アクティブ化されます。

于 2010-09-13T12:21:26.677 に答える
10

exist最上位レベルを見る前に、すべてのレベルを確認してください。

if (exists $ref->{A} and exists $ref->{A}{B} and exists $ref->{A}{B}{$key}) {
}

それが煩わしい場合は、いつでもCPANを見ることができます。たとえば、 がありますHash::NoVivify

于 2010-09-13T11:54:13.813 に答える
6

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));
于 2010-09-13T15:18:52.623 に答える
0

かなり醜いですが、 $ref が複雑な式であり、繰り返しの存在テストで使用したくない場合:

if ( exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key} ) {
于 2010-09-13T13:36:18.863 に答える