Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Намного лучше использовать что-то вроде 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. Все остальное - тестирование и демонстрация:

#!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

Ответ 2

Вы можете использовать autovivification прагму для деактивации автоматического создания ссылок:

use strict;
use warnings;
no autovivification;

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

print join ', ', keys %foo;

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

Ответ 3

Проверяйте каждый уровень для exist ence перед тем, как смотреть на верхний уровень.

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

Если вы обнаружите, что это раздражает, вы всегда можете посмотреть CPAN. Например, есть Hash::NoVivify.

Ответ 4

Взгляните на 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));

Ответ 5

Довольно уродливый, но если $ref является сложным выражением, которое вы не хотите использовать при повторных тестах:

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