C'est bien mieux d'utiliser quelque chose comme le module d'autovivification pour désactiver cette fonctionnalité, ou d'utiliser Data::Diver. Cependant, c'est l'une des tâches simples que j'attendrais d'un programmeur qu'il sache faire lui-même. Même si vous n'utilisez pas cette technique ici, vous devriez la connaître pour d'autres problèmes. C'est essentiellement ce que Data::Diver
fait une fois que vous avez enlevé son interface.
C'est facile une fois que vous avez compris le truc pour parcourir une structure de données (si vous ne voulez pas utiliser un module qui le fait pour vous). Dans mon exemple, je crée une sous-routine check_hash
qui prend une référence de hachage et une référence de tableau des clés à vérifier. Elle vérifie un niveau à la fois. Si la clé n'est pas là, rien n'est renvoyé. Si la clé est présente, le hachage est élagué pour juste cette partie du chemin et on essaie à nouveau avec la prochaine clé. Le truc est que $hash
est toujours la prochaine partie de l'arborescence à vérifier. Je mets le exists
dans un eval
au cas où le prochain niveau n'est pas une référence de hachage. Le truc est de ne pas échouer si la valeur du hachage à la fin du chemin est une sorte de valeur fausse. 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 soyez pas effrayé par tout le code dans la prochaine partie. La partie importante est juste la sous-routine check_hash
. Tout le reste est du test et de la 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 déversement 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 une autre vérification sauf que 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 autre chose. C'est juste une question de fournir la bonne vérification une fois que vous avez vérifié que le chemin existe. Dans cet exemple, je passe une référence de sous-routine qui vérifiera la valeur avec laquelle j'ai laissé. Je peux vérifier n'importe quoi que je veux :
#!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