2010-09-13 3 views
24

Si je understand correctly, appel if (exists $ref->{A}->{B}->{$key}) { ... } existera $ref->{A} et $ref->{A}->{B} même s'ils n'existaient pas avant le if!Comment puis-je vérifier si une clé existe dans un hachage Perl profond?

Cela semble hautement indésirable. Alors, comment dois-je vérifier si une clé de hachage "profonde" existe?

+3

Je suis étonné que ce n'est pas dans le perlfaq, étant donné qu'il est plus FA que la plupart des Qs déjà là . Donnez-moi quelques minutes et je corrigerai cela :) –

+9

Oh regarde, là c'est dans perlfaq4: [Comment puis-je vérifier si une clé existe dans un hash multiniveau?] (Http://faq.perl.org/ perlfaq4.html # How_can_I_check_if_a). C'est essentiellement un résumé de ce fil. Merci StackOverflow :) –

Répondre

35

Il est préférable d'utiliser quelque chose comme le module autovivification pour désactiver cette fonction, ou d'utiliser Data::Diver. Cependant, c'est une des tâches simples que je m'attendrais à ce qu'un programmeur sache faire seul. Même si vous n'utilisez pas cette technique ici, vous devriez le savoir pour d'autres problèmes. C'est essentiellement ce que fait Data::Diver une fois que vous avez retiré son interface.

Ceci est facile une fois que vous avez l'astuce de marcher une structure de données (si vous ne voulez pas utiliser un module qui le fait pour vous). Dans mon exemple, je crée un sous-programme check_hash 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à, elle élague le hachage juste à cette partie du chemin et essaie à nouveau avec la clé suivante. L'astuce est que $hash est toujours la prochaine partie de l'arbre à vérifier. Je mets le exists dans un eval dans le cas où le niveau suivant n'est pas une référence de hachage. 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 soyez pas effrayé par tout le code dans le bit suivant. La partie importante est juste le sous-programme check_hash. Tout le reste est le 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 la décharge 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 du chemin choisi est vrai, ou une chaîne, ou une autre référence de hachage, ou peu importe. 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-programme qui va vérifier la valeur que j'ai laissée avec.Je peux vérifier tout ce que j'aime:

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

+1 merci pour l'exemple élaboré! –

8

Vérifiez chaque niveau pour exist 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 que gênant, vous pouvez toujours regarder CPAN. Par exemple, il y a Hash::NoVivify.

+1

un peu sale, n'est-ce pas? –

+0

aussi, existe-t-il une différence entre '$ ref -> {A} {B} {C}' et '$ ref -> {A} -> {B} -> {C}'? –

+4

@David Non, il n'y a pas de différence. La seule flèche qui fait quoi que ce soit est la première. Les flèches entre les '{}' et '[]' successifs sont inutiles et il semble généralement préférable de les laisser de côté. – hobbs

13

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; 

Il est lexical, ce qui signifie qu'il ne fera que le désactiver à l'intérieur du champ spécifié dans

+0

'Impossible de localiser autovivification.pm dans @ INC '?! –

+3

Cette erreur signifie que vous devez télécharger et installer le pragma 'autovivification' de CPAN, comme vous le feriez avec n'importe quel autre module. – toolic

+0

Donc, je autovivification travail sans autovivification? –

0

assez laid , mais si $ ref est une expression compliquée que vous ne voulez pas utiliser dans des tests répétés existe:

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

C'est une abomination. Je fais les yeux croisés en essayant juste de le regarder. Vous créez aussi jusqu'à 'n - 1' (où' n' est le nombre de niveaux dans le hachage) hashrefs anonymes dans le seul but d 'éviter l' autovivication dans le hash cible (vous auto - validez dans le hashref anonyme à la place). Je me demande quelle est la performance par rapport aux appels multiples à «exister» du code sain. –

+0

@Chas. Owens: la performance est probablement pire, peut-être plusieurs fois pire, ce qui n'a pas d'importance du tout, étant donné que cela prend un temps insignifiant. – ysth

+1

Il est en fait préférable dans le cas où toutes les clés existent d'environ trois fois. La version sensée commence à gagner après cela, mais ils peuvent tous s'exécuter plus d'un million de fois par seconde, donc il n'y a aucun avantage réel de toute façon. Voici le [benchmark] (http://codepad.org/tXIMrpVW) que j'ai utilisé. –

5

Jetez un oeil à Data::Diver. .: par exemple

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)); 

Questions connexes