2010-09-13 5 views
24

Wenn ich understand correctly, ruft if (exists $ref->{A}->{B}->{$key}) { ... } wird $ref->{A} und $ref->{A}->{B} ins Leben, auch wenn sie nicht existierten, bevor die if!Wie kann ich prüfen, ob ein Schlüssel in einem tiefen Perl-Hash existiert?

Dies scheint sehr unerwünscht. Also, wie sollte ich prüfen, ob ein "tiefer" Hash-Schlüssel existiert?

+3

Ich bin erstaunt, dass dies nicht in der perlfaq ist, wenn man bedenkt es mehr FA ist als die meisten der Qs bereits dort in . Geben Sie mir ein paar Minuten und ich werde das beheben :) –

+9

Oh schau, da ist es in Perlfaq4: [Wie kann ich überprüfen, ob ein Schlüssel in einem Multilevel-Hash existiert?] (Http://faq.perl.org/ perlfaq4.html # How_can_I_check_if_a). Es ist im Wesentlichen eine Zusammenfassung dieses Threads. Danke StackOverflow :) –

Antwort

35

Es ist viel besser so etwas wie das autovivification Modul zu verwenden, die Funktion zu deaktivieren, oder Data::Diver zu verwenden. Dies ist jedoch eine der einfachen Aufgaben, von denen ich erwarte, dass ein Programmierer es selbstständig macht. Auch wenn Sie diese Technik hier nicht anwenden, sollten Sie sie für andere Probleme kennen. Dies ist im Wesentlichen was Data::Diver tut, sobald Sie seine Schnittstelle entfernen.

Dies ist einfach, sobald Sie den Trick des Gehens einer Datenstruktur erhalten (wenn Sie kein Modul verwenden möchten, das es für Sie tut). In meinem Beispiel erstelle ich eine Subroutine check_hash, die eine Hash-Referenz und eine Array-Referenz der zu überprüfenden Schlüssel verwendet. Es prüft jeweils eine Ebene. Wenn der Schlüssel nicht vorhanden ist, gibt er nichts zurück. Wenn der Schlüssel dort ist, wird der Hash auf genau diesen Teil des Pfades zurückgesetzt und es wird erneut mit dem nächsten Schlüssel versucht. Der Trick ist, dass $hash immer der nächste Teil des zu überprüfenden Baumes ist. Ich setze die exists in eine eval für den Fall, dass die nächste Ebene keine Hash-Referenz ist. Der Trick besteht darin, nicht zu versagen, wenn der Hash-Wert am Ende des Pfades eine Art falscher Wert ist. Hier ist der wichtige Teil der Aufgabe:

sub check_hash { 
    my($hash, $keys) = @_; 

    return unless @$keys; 

    foreach my $key (@$keys) { 
     return unless eval { exists $hash->{$key} }; 
     $hash = $hash->{$key}; 
     } 

    return 1; 
    } 

Lassen Sie sich nicht durch den ganzen Code im nächsten Bit erschrecken. Der wichtige Teil ist nur das check_hash Unterprogramm. Alles andere ist die Erprobung und Demonstration:

#!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'; 
    } 

Hier ist der Ausgang (abzüglich der Daten-Dump):

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 

Jetzt könnten Sie eine andere Kontrolle statt exists haben wollen. Vielleicht möchten Sie überprüfen, ob der Wert im ausgewählten Pfad wahr ist, oder eine Zeichenfolge oder eine andere Hash-Referenz oder was auch immer. Das ist nur eine Frage des richtigen Checks, sobald Sie verifiziert haben, dass der Pfad existiert. In diesem Beispiel übergebe ich eine Subroutinenreferenz, die den Wert prüft, mit dem ich aufgehört habe.Ich kann für alles überprüfen Ich mag:

#!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'; 
    } 

und seine Ausgabe:

 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 danke für das aufwendige Beispiel! –

8

Überprüfen Sie jedes Level auf exist bevor Sie die oberste Ebene betrachten.

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

Wenn Sie feststellen, dass ärgerlich Sie immer auf CPAN aussehen könnte. Zum Beispiel gibt es Hash::NoVivify.

+1

ein bisschen dreckig, oder? –

+0

gibt es auch einen Unterschied zwischen '$ ref -> {A} {B} {C}' und '$ ref -> {A} -> {B} -> {C}'? –

+4

@David Nein, es gibt keinen Unterschied. Der einzige Pfeil, der etwas bewirkt, ist der erste. Pfeile zwischen aufeinanderfolgenden '{}' und '[]' sind unnötig und es sieht normalerweise besser aus, sie auszulassen. – hobbs

13

könnten Sie verwenden die autovivification Pragma die automatische Erstellung von Referenzen zu deaktivieren.

use strict; 
use warnings; 
no autovivification; 

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

print join ', ', keys %foo; 

Es ist auch lexikalische, was bedeutet es nur es Sie darin angeben, innerhalb des Schutzbereichs deaktivieren werden

+0

'Kann autovivification.pm in @ INC nicht finden ?! –

+3

Dieser Fehler bedeutet, dass Sie das 'autovivification' Pragma von CPAN wie jedes andere Modul herunterladen und installieren müssen. – toolic

+0

Also ich autovivification arbeiten ohne autovivification? –

0

Ziemlich hässlich , aber wenn $ ref ein komplizierter Ausdruck ist, dass Sie nicht wiederholt existiert Tests verwenden möchten:

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

Das ist ein Greuel. Ich schiele nur, um es mir anzusehen. Sie erstellen nach oben auch 'n - 1 'anonymer hashrefs für den alleinigen Zweck (wobei' n' die Anzahl der Ebenen in der Hash ist) autovivication im Ziel Hash zu vermeiden (Sie in den anonymen hashref autovivify statt). Ich frage mich, wie die Leistung im Vergleich zu den mehreren Aufrufen zu "Existieren" des gesunden Codes ist. –

+0

@Chas. Owens: Die Leistung ist wahrscheinlich schlechter, vielleicht sogar um ein Vielfaches schlechter, was gar nicht so wichtig ist, da es eine triviale Menge an Zeit kostet. – ysth

+1

Es ist tatsächlich besser in dem Fall, in dem alle Schlüssel ungefähr dreimal vorhanden sind. Die gesunde Version beginnt danach zu gewinnen, aber sie können alle über eine Million Mal in der Sekunde ausgeführt werden, also gibt es keinen wirklichen Nutzen. Hier ist der [Benchmark] (http://codepad.org/tXIMrpVW), den ich verwendet habe. –

5

einen Blick aufNehmen. Z.B .:

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