Как я могу проверить, существует ли ключ в глубоком хэше Perl?

Если Я правильно понял, называя if (exists $ref->{A}->{B}->{$key}) { ... } возникнет $ref->{A} и $ref->{A}->{B} даже если они не существовали до if!

Это кажется крайне нежелательным. Итак, как я должен проверить, существует ли "глубокий" хэш-ключ?

5 ответов


это гораздо лучше использовать что-то вроде autovivification модуль, чтобы отключить эту функцию, или использовать Data:: Diver. Однако это одна из простых задач, которую я ожидал бы от программиста, чтобы он знал, как это сделать самостоятельно. Даже если вы не используете эту технику здесь, вы должны знать ее для других проблем. Это по существу то, что Data::Diver делает, как только вы удаляете его интерфейс.

это легко, как только вы получите трюк ходьбы данных структура (если вы не хотите использовать модуль, который делает это для вас). В моем примере я создаю check_hash подпрограмма, которая принимает хэш-ссылку и ссылку на массив ключей для проверки. Он проверяет уровень за уровнем. Если ключа нет, он ничего не возвращает. Если ключ есть, он обрезает хэш только до этой части пути и пытается снова со следующим ключом. Фокус в том, что $hash всегда следующая часть дерева, чтобы проверить. Я поставил exists на eval в случае, если следующий уровень это не хэш-ссылка. Фокус в том, чтобы не потерпеть неудачу, если хэш-значение в конце пути является своего рода ложным значением. Вот важная часть задания:--17-->

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;

Он также лексический, то есть он будет деактивировать его только внутри области, в которой вы его укажете.


проверьте каждый уровень для existence, прежде чем смотреть на верхнем уровне.

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-сложное выражение, которое вы не хотите использовать в повторяющихся тестах exists:

if ( exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key} ) {