Comment puis-je vérifier si une clé existe dans un hachage Perl profond?
Si Je comprendre correctement, appelant if (exists $ref->{A}->{B}->{$key}) { ... }
va du printemps à l'existence $ref->{A}
et $ref->{A}->{B}
même si ils n'existaient pas avant l' if
!
Cela semble hautement indésirables. Alors comment puis-je vérifier si une clé de hachage "profonde" existe?
5 réponses
C'est beaucoup mieux d'utiliser quelque chose comme autovivification module pour désactiver cette fonctionnalité, ou d'utiliser Données::Plongeur. Cependant, c'est l'une des tâches simples que je m'attendrais à ce qu'un programmeur sache faire par lui-même. Même si vous n'utilisez pas cette technique ici, vous devriez le savoir pour d'autres problèmes. C'est essentiellement ce que Data::Diver
le fait une fois que vous enlevez son interface.
c'est facile une fois que vous avez l'astuce de parcourir une donnée la structure (si vous ne souhaitez pas utiliser un module qui le fait pour vous). Dans mon exemple, j'ai créer un check_hash
sous-programme qui prend une référence de hachage et une référence de tableau de clés à vérifier. Il vérifie un niveau à la fois. Si la clé n'est pas là, elle ne retourne rien. Si la clé est là, il taille le hachage à juste cette partie du chemin et tente à nouveau avec la touche suivante. Le truc, c'est que $hash
est toujours de la partie suivante de l'arbre pour vérifier. J'ai mis le exists
dans un eval
au cas où le niveau suivant n'est pas une référence hash. L'astuce est de ne pas échouer si la valeur de hachage à la fin du chemin est une sorte de fausse valeur. Voici la partie importante de la tâche:
sub check_hash {
my( $hash, $keys ) = @_;
return unless @$keys;
foreach my $key ( @$keys ) {
return unless eval { exists $hash->{$key} };
$hash = $hash->{$key};
}
return 1;
}
ne pas avoir peur de tout le code dans le prochain morceau. L'important, c'est juste le check_hash
sous-routine. Tout le reste est de test et de démonstration:
#!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';
}
Voici la sortie (moins le dump de données):
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
Maintenant, vous voudrez peut-être avoir un autre chèque au lieu de exists
. Peut-être que vous voulez vérifier que la valeur au chemin choisi est vraie, ou une chaîne, ou une autre Référence de hachage, ou quoi que ce soit. C'est juste une question de fournir le bon contrôle une fois que vous avez vérifié que le chemin existe. Dans cet exemple, je passe une référence de sous-programme qui vérifiera la valeur que j'ai laissée. Je peux vérifier pour tout ce que j'aime bien:
#!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';
}
et sa sortie:
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
Vous pouvez utiliser le autovivification pragma pour désactiver la création automatique de références:
use strict;
use warnings;
no autovivification;
my %foo;
print "yes\n" if exists $foo{bar}{baz}{quux};
print join ', ', keys %foo;
c'est aussi lexical, ce qui signifie qu'il ne le désactivera qu'à l'intérieur de la portée spécifiée.
Vérifiez chaque niveau pour exist
nce avant de regarder le niveau supérieur.
if (exists $ref->{A} and exists $ref->{A}{B} and exists $ref->{A}{B}{$key}) {
}
Si vous trouvez ça ennuyeux, vous pouvez toujours chercher sur CPAN. Par exemple, il y a Hash::NoVivify
.
regardez Données:: Diver. E. g.:
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));
assez laid, mais si $ ref est une expression compliquée que vous ne voulez pas utiliser dans les tests d'exists répétés:
if ( exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key} ) {