Ответ 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