2010-09-13 18 views
24

Jeśli zadzwonię pod numer if (exists $ref->{A}->{B}->{$key}) { ... }$ref->{A} i $ref->{A}->{B}, nawet jeśli nie istniały przed if!Jak mogę sprawdzić, czy klucz istnieje w głębokiej mieszance Perla?

To wydaje się bardzo niechciane. Jak więc sprawdzić, czy istnieje "głęboki" klucz skrótu?

+3

Jestem zdumiony, że nie jest w perlfaq, biorąc pod uwagę, że to więcej niż większość FA QS już tam . Daj mi kilka minut, a ja to naprawię :) –

+9

Oh look, tam to jest w perlfaq4: [Jak mogę sprawdzić, czy klucz istnieje w wielopoziomowym haszyszu?] (Http://faq.perl.org/ perlfaq4.html # How_can_I_check_if_a). Jest to w zasadzie podsumowanie tego wątku. Dzięki StackOverflow :) –

Odpowiedz

35

To znacznie lepiej użyć czegoś podobnego modułu autovivification, aby wyłączyć tę funkcję, lub użyć Data::Diver. Jest to jednak jedno z prostych zadań, które mógłbym oczekiwać od programisty. Nawet jeśli nie użyjesz tej techniki tutaj, powinieneś znać ją dla innych problemów. Zasadniczo robi to Data::Diver po zdjęciu interfejsu.

Jest to łatwe, gdy masz ochotę przejść przez strukturę danych (jeśli nie chcesz używać modułu, który robi to za Ciebie). W moim przykładzie tworzę podprogram check_hash, który pobiera odwołanie do tablicy i odwołanie do tablicy kluczy do sprawdzenia. Sprawdza jeden poziom na raz. Jeśli klucza tam nie ma, nic nie zwraca. Jeśli klucz znajduje się w tym miejscu, przycina on skrót do tej części ścieżki i próbuje ponownie za pomocą następnego klawisza. Sztuczka polega na tym, że $hash jest zawsze następną częścią drzewa do sprawdzenia. Umieszczam exists w eval na wypadek, gdyby następny poziom nie był referencją skrótu. Sztuczka nie kończy się niepowodzeniem, jeśli wartość skrótu na końcu ścieżki jest jakąś fałszywą wartością. Oto najważniejsza część zadania:

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

    return unless @$keys; 

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

    return 1; 
    } 

Nie bój się całego kodu w następnym fragmencie. Ważną częścią jest podprogram check_hash. Wszystko inne jest testowanie i demonstracji:

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

Oto wyjście (minus zrzut danych):

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 

Teraz możesz mieć jakąś inną kontrolę zamiast exists. Być może chcesz sprawdzić, czy wartość na wybranej ścieżce jest prawdą, lub ciągiem znaków, lub innym hashowaniem, lub cokolwiek innego. To tylko kwestia dostarczenia właściwego czeku po sprawdzeniu, czy ścieżka istnieje. W tym przykładzie przekazuję podprocedurę referencyjną, która sprawdzi wartość, z którą zrezygnowałem.Mogę sprawdzić cokolwiek lubię:

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

a jego produkcja:

 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 dzięki za misterny przykład! –

8

Sprawdź każdy poziom dla exist, zanim spojrzysz na najwyższy poziom.

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

Jeśli okaże się, że irytujące może zawsze wyglądać na CPAN. Na przykład istnieje Hash::NoVivify.

+1

trochę brudny, prawda? –

+0

również, czy istnieje różnica między '$ ref -> {A} {B} {C}' a '$ ref -> {A} -> {B} -> {C}'? –

+4

@ David Nie, nie ma różnicy. Jedyną strzałą, która cokolwiek robi, jest pierwsza. Strzałki pomiędzy kolejnymi '{}' i '[]' są niepotrzebne i zazwyczaj lepiej jest je pominąć. – hobbs

13

Można użyć autovivification pragmy aby wyłączyć automatyczne tworzenie odnośników.

use strict; 
use warnings; 
no autovivification; 

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

print join ', ', keys %foo; 

To także leksykalne, czyli będzie to tylko wyłączyć go wewnątrz zakresu określonego go w

+0

'Nie można znaleźć autovivification.pm w @ INC' ?! –

+3

Błąd ten oznacza, że ​​musisz pobrać i zainstalować 'pragovivification' z CPAN, tak jak każdy inny moduł. – toolic

+0

Więc mam autoweryfikację działającą bez autoworyzacji? –

0

dość brzydki , ale jeśli $ ref jest skomplikowane wyrażenie, które nie chcą korzystać z powtarzających istnieje testów:

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

To jest obrzydliwość. Idę zezem, próbując na nie spojrzeć. Tworzysz także do 'n - 1' (gdzie' n' to liczba poziomów w haszcie) anonymous hashrefs wyłącznie w celu uniknięcia autovivication w docelowym hash (zamiast tego autoewizujesz w anonimowym hashref). Zastanawiam się, jaka jest wydajność w porównaniu do wielokrotnych połączeń z "istniejącym" w rozsądnym kodzie. –

+0

@Chas. Owens: występ jest prawdopodobnie gorszy, może nawet gorszy, co nie ma znaczenia, biorąc pod uwagę, że zajmuje to trochę czasu. – ysth

+1

W rzeczywistości jest lepiej w przypadku, gdy wszystkie klucze istnieją około trzy razy. Wersja zdrowa zaczyna wygrywać po tym, ale wszystkie mogą być wykonywane ponad milion razy na sekundę, więc nie ma żadnej realnej korzyści w żaden sposób. Oto użyty [benchmark] (http://codepad.org/tXIMrpVW). –

5

Spójrz Data::Diver. Np .:

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)); 
Powiązane problemy