2010-09-13 31 views
24

Nếu tôi understand correctly, gọi số if (exists $ref->{A}->{B}->{$key}) { ... } sẽ bắt đầu tồn tại $ref->{A}$ref->{A}->{B} ngay cả khi chúng không tồn tại trước if!Làm cách nào để kiểm tra xem khóa có tồn tại trong một hàm băm Perl sâu?

Điều này có vẻ rất không mong muốn. Vậy làm cách nào để kiểm tra xem khóa băm "sâu" có tồn tại không?

+3

Tôi ngạc nhiên rằng đây không phải là trong perlfaq, coi đó là hơn FA hơn hầu hết các Q đã có trong đó . Hãy cho tôi một vài phút và tôi sẽ sửa chữa điều đó :) –

+9

Oh, có trong perlfaq4: [Làm thế nào tôi có thể kiểm tra xem một khóa tồn tại trong một băm đa cấp?] (Http://faq.perl.org/ perlfaq4.html # How_can_I_check_if_a). Đó là bản tóm tắt về chủ đề này. Cảm ơn StackOverflow :) –

Trả lời

35

Tốt hơn bạn nên sử dụng mô-đun autovivification để tắt tính năng đó hoặc sử dụng Data::Diver. Tuy nhiên, đây là một trong những nhiệm vụ đơn giản mà tôi mong đợi một lập trình viên biết cách tự làm. Ngay cả khi bạn không sử dụng kỹ thuật này ở đây, bạn nên biết nó cho các vấn đề khác. Đây thực chất là những gì mà Data::Diver đang thực hiện khi bạn bỏ qua giao diện của nó.

Điều này rất dễ dàng khi bạn nhận được mẹo để đi bộ một cấu trúc dữ liệu (nếu bạn không muốn sử dụng mô-đun thực hiện điều đó cho bạn). Trong ví dụ của tôi, tôi tạo một chương trình con check_hash lấy tham chiếu băm và tham chiếu mảng của các phím để kiểm tra. Nó kiểm tra một cấp tại một thời điểm. Nếu khóa không có ở đó, nó sẽ không trả về gì cả. Nếu khóa ở đó, nó sẽ băm băm chỉ là một phần của đường dẫn và thử lại bằng phím tiếp theo. Bí quyết là $hash luôn là phần tiếp theo của cây để kiểm tra. Tôi đặt exists trong một số eval trong trường hợp cấp tiếp theo không phải là tham chiếu băm. Bí quyết không phải là thất bại nếu giá trị băm ở cuối đường dẫn là một loại giá trị sai nào đó. Đây là một phần quan trọng của tác vụ:

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

    return unless @$keys; 

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

    return 1; 
    } 

Đừng sợ bởi tất cả mã trong bit tiếp theo. Phần quan trọng chỉ là chương trình con check_hash. Mọi thứ khác là thử nghiệm và trình diễn:

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

Dưới đây là đầu ra (trừ các bãi chứa dữ liệu):

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 

Bây giờ, bạn có thể muốn có một số kiểm tra khác thay vì exists. Có thể bạn muốn kiểm tra xem giá trị tại đường dẫn đã chọn có đúng hay là một chuỗi hoặc một tham chiếu băm khác hay bất kỳ thứ gì. Đó chỉ là vấn đề cung cấp kiểm tra đúng khi bạn đã xác minh rằng đường dẫn tồn tại. Trong ví dụ này, tôi chuyển một tham chiếu chương trình con sẽ kiểm tra giá trị mà tôi đã bỏ đi.Tôi có thể kiểm tra bất cứ điều gì tôi thích:

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

Và sản lượng của nó:

 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 cảm ơn cho ví dụ phức tạp! –

8

Kiểm tra mọi cấp độ cho exist ence trước khi xem ở cấp cao nhất.

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

Nếu bạn thấy khó chịu, bạn luôn có thể xem CPAN. Ví dụ: có Hash::NoVivify.

+1

một chút bẩn, phải không? –

+0

cũng có sự khác biệt giữa '$ ref -> {A} {B} {C}' và '$ ref -> {A} -> {B} -> {C}' không? –

+4

@David Không, không có sự khác biệt. Mũi tên duy nhất làm bất cứ điều gì là thứ nhất. Các mũi tên giữa '{}' và '[]' liên tiếp là không cần thiết và thường có vẻ tốt hơn để loại bỏ chúng. – hobbs

13

Bạn có thể sử dụng autovivification pragma để tắt việc tạo ra tự động của tài liệu tham khảo:.

use strict; 
use warnings; 
no autovivification; 

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

print join ', ', keys %foo; 

Nó cũng từ vựng, nghĩa là nó sẽ chỉ tắt nó bên trong phạm vi bạn xác định nó trong

+0

'Không thể định vị autovivification.pm trong @ INC' ?! –

+3

Lỗi đó có nghĩa là bạn cần tải xuống và cài đặt pragma 'autovivification' từ CPAN, giống như bất kỳ mô-đun nào khác. – toolic

+0

Vì vậy, tôi có autovivification làm việc mà không cần autovivification? –

0

Khá xấu xí , nhưng nếu $ ref là một biểu thức phức tạp mà bạn không muốn sử dụng trong các thử nghiệm tồn tại lặp lại:

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

Đó là một abomination. Tôi đi ngang mắt chỉ cố nhìn vào nó. Bạn cũng đang tạo tối đa 'n - 1' (trong đó' n' là số lượng hash trong bảng băm) cho mục đích duy nhất là tránh autovivication trong hash mục tiêu (bạn tự động thay thế trong hashref ẩn danh). Tôi tự hỏi những gì hiệu suất là như so với nhiều cuộc gọi để 'tồn tại' của mã sane. –

+0

@Chas. Owens: hiệu suất có thể tồi tệ hơn, có lẽ tệ hơn nhiều lần, điều này không quan trọng chút nào vì nó mất một lượng thời gian nhỏ. – ysth

+1

Nó thực sự là tốt hơn trong trường hợp là tất cả các phím tồn tại khoảng ba lần. Phiên bản lành mạnh bắt đầu giành chiến thắng sau đó, nhưng tất cả họ có thể thực hiện hơn một triệu lần một giây, vì vậy không có lợi ích thực sự nào cả. Đây là [điểm chuẩn] (http://codepad.org/tXIMrpVW) mà tôi đã sử dụng. –

5

Hãy xem Data::Diver. Ví dụ:

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)); 
Các vấn đề liên quan