2008-10-01 24 views
5

Nếu bạn có băm (hoặc tham chiếu đến băm) trong perl với nhiều tham số và bạn muốn lặp qua tất cả các giá trị, cách tốt nhất để làm điều đó là gì. Nói cách khác, nếu chúng ta có $ f -> {$ x} {$ y}, tôi muốn một cái gì đó giống nhưTraversing băm đa chiều trong Perl

foreach ($x, $y) (deep_keys %{$f}) 
{ 
} 

thay vì

foreach $x (keys %f) 
    { 
    foreach $y (keys %{$f->{$x}) 
    { 
    } 
} 
+0

Ngữ cảnh danh sách sẽ không trả lại danh sách được phân đoạn, mà là danh sách được làm phẳng, vì vậy bạn sẽ không nhận được hai lần quay trở lại cùng một lúc. Tốt hơn là deep_keys chuyển trở lại tham chiếu mảng. – Axeman

Trả lời

11

Đây là một tùy chọn. Này làm việc cho băm tùy ý sâu sắc:

sub deep_keys_foreach 
{ 
    my ($hashref, $code, $args) = @_; 

    while (my ($k, $v) = each(%$hashref)) { 
     my @newargs = defined($args) ? @$args :(); 
     push(@newargs, $k); 
     if (ref($v) eq 'HASH') { 
      deep_keys_foreach($v, $code, \@newargs); 
     } 
     else { 
      $code->(@newargs); 
     } 
    } 
} 

deep_keys_foreach($f, sub { 
    my ($k1, $k2) = @_; 
    print "inside deep_keys, k1=$k1, k2=$k2\n"; 
}); 
+1

Tôi thích điều này! Cảm ơn câu trả lời ngắn gọn và khả thi mà hoạt động ra khỏi hộp. –

1

Nó đủ dễ dàng nếu tất cả các bạn muốn làm hoạt động trên các giá trị, nhưng nếu bạn muốn hoạt động trên các khóa, bạn cần thông số kỹ thuật về mức độ có thể phục hồi.

a. Ví dụ: bạn có thể chỉ định các khóa là "$level1_key.$level2_key.$level3_key" --hoặc bất kỳ dấu tách nào, biểu thị các cấp.

b. Hoặc bạn có thể có một danh sách các phím.

Tôi khuyên bạn nên sử dụng sau.

  • Cấp có thể được hiểu bởi @$key_stack

  • và phím địa phương nhất là $key_stack->[-1].

  • Đường dẫn có thể được tái tạo bởi: join('.', @$key\_stack)

Code:

use constant EMPTY_ARRAY => []; 
use strict;  
use Scalar::Util qw<reftype>; 

sub deep_keys (\%) { 
    sub deeper_keys { 
     my ($key_ref, $hash_ref) = @_; 
     return [ $key_ref, $hash_ref ] if reftype($hash_ref) ne 'HASH'; 
     my @results; 

     while (my ($key, $value) = each %$hash_ref) { 
      my $k = [ @{ $key_ref || EMPTY_ARRAY }, $key ]; 
      push @results, deeper_keys($k, $value); 
     } 
     return @results; 
    } 

    return deeper_keys(undef, shift); 
} 

foreach my $kv_pair (deep_keys %$f) { 
    my ($key_stack, $value) = @_; 
    ... 
} 

này đã được thử nghiệm trong Perl 5.10.

2

Hãy nhớ rằng danh sách Perl và băm không có kích thước và do đó không thể đa chiều. Những gì bạn có thể có một mục băm được đặt để tham chiếu một băm hoặc danh sách khác. Điều này có thể được sử dụng để tạo ra các cấu trúc đa chiều giả.

Khi bạn nhận ra điều này, mọi thứ trở nên dễ dàng. Ví dụ:

sub f($) { 
    my $x = shift; 
    if(ref $x eq 'HASH') { 
    foreach(values %$x) { 
     f($_); 
    } 
    } elsif(ref $x eq 'ARRAY') { 
    foreach(@$x) { 
     f($_); 
    } 
    } 
} 

Thêm bất kỳ việc gì khác cần được thực hiện ngoài việc duyệt qua cấu trúc, tất nhiên.

Một cách tiện lợi để thực hiện những gì bạn cần là chuyển một tham chiếu mã được gọi từ bên trong f. Bằng cách sử dụng tạo mẫu phụ, bạn thậm chí có thể thực hiện các cuộc gọi trông giống như các chức năng bản đồ và bản đồ của Perl.

1

Nếu bạn đang làm việc với các dữ liệu cây đi hơn hai cấp độ sâu, và bạn thấy mình muốn đi cây, trước tiên bạn nên xem xét mà bạn sẽ kiếm được nhiều của công việc phụ cho chính mình nếu bạn có kế hoạch reimplementing tất cả mọi thứ bạn cần phải làm bằng tay trên băm băm của băm khi có rất nhiều lựa chọn thay thế tốt có sẵn (search CPAN for "Tree").

Không biết yêu cầu dữ liệu của bạn thực sự là gì, tôi sẽ hướng dẫn bạn một cách mù quáng tại tutorial for Tree::DAG_Node để giúp bạn bắt đầu.

Điều đó nói rằng, Axeman là chính xác, một hashwalk được thực hiện dễ dàng nhất với đệ quy. Dưới đây là một ví dụ để giúp bạn bắt đầu nếu bạn cảm thấy bạn hoàn toàn phải giải quyết vấn đề của bạn với băm băm băm:

 
#!/usr/bin/perl 
use strict; 
use warnings; 

my %hash = (
    "toplevel-1" => 
    { 
     "sublevel1a" => "value-1a", 
     "sublevel1b" => "value-1b" 
    }, 
    "toplevel-2" => 
    { 
     "sublevel1c" => 
     { 
      "value-1c.1" => "replacement-1c.1", 
      "value-1c.2" => "replacement-1c.2" 
     }, 
     "sublevel1d" => "value-1d" 
    } 
); 

hashwalk(\%hash); 

sub hashwalk 
{ 
    my ($element) = @_; 
    if(ref($element) =~ /HASH/) 
    { 
     foreach my $key (keys %$element) 
     { 
      print $key," => \n"; 
      hashwalk($$element{$key}); 
     } 
    } 
    else 
    { 
     print $element,"\n"; 
    } 
} 

Nó sẽ ra:

 
toplevel-2 => 
sublevel1d => 
value-1d 
sublevel1c => 
value-1c.2 => 
replacement-1c.2 
value-1c.1 => 
replacement-1c.1 
toplevel-1 => 
sublevel1a => 
value-1a 
sublevel1b => 
value-1b 

Lưu ý rằng bạn KHÔNG THỂ dự đoán theo thứ tự nào các phần tử băm sẽ được vượt qua trừ khi bạn gắn băm thông qua Tie :: IxHash hoặc tương tự - một lần nữa, nếu bạn định trải qua nhiều công việc, tôi khuyên bạn nên sử dụng một mô-đun cây.

1

Không có cách nào để có được ngữ nghĩa bạn mô tả bởi vì foreach lặp qua danh sách một phần tử tại một thời điểm. Bạn cần phải có deep_keys trả lại một LoL (danh sách các danh sách) để thay thế. Thậm chí điều đó không làm việc trong trường hợp chung của một cấu trúc dữ liệu tùy ý. Có thể có các mức độ băm nhỏ khác nhau, một số mức có thể là ARRAY refs, v.v.

Cách thực hiện điều này là viết một hàm có thể đi theo cấu trúc dữ liệu tùy ý và áp dụng gọi lại ở mỗi "lá" (nghĩa là, giá trị không tham chiếu). bmdhacks' answer là điểm bắt đầu. Chức năng chính xác sẽ khác nhau tùy thuộc vào những gì bạn muốn làm ở mỗi cấp độ. Nó khá đơn giản nếu tất cả các bạn quan tâm là các giá trị lá. Mọi thứ trở nên phức tạp hơn nếu bạn quan tâm đến các phím, chỉ mục, v.v. đã đưa bạn đến chiếc lá.

2

Bạn cũng có thể lận mảng đa chiều nếu bạn luôn có tất cả các giá trị quan trọng, hoặc bạn chỉ không cần phải truy cập vào các cấp độ cá nhân như mảng riêng biệt:

$arr{"foo",1} = "one"; 
$arr{"bar",2} = "two"; 

while(($key, $value) = each(%arr)) 
{ 
    @keyValues = split($;, $key); 
    print "key = [", join(",", @keyValues), "] : value = [", $value, "]\n"; 
} 

này sử dụng tách subscript "$;" làm dấu phân cách cho nhiều giá trị trong khóa.

12

Giai đoạn một: không phát minh lại bánh xe :)

Một nhanh chóng search on CPAN ném lên sự vô cùng hữu ích Data::Walk. Xác định một chương trình con để xử lý từng nút và bạn được sắp xếp

use Data::Walk; 

my $data = { # some complex hash/array mess }; 

sub process { 
    print "current node $_\n"; 
} 

walk \&process, $data; 

Và Bob là chú của bạn. Lưu ý rằng nếu bạn muốn chuyển một giá trị băm để đi bộ, bạn sẽ cần chuyển một tham chiếu đến nó (xem perldoc perlref), như sau (nếu không nó cũng sẽ thử và xử lý khóa băm của bạn!):

walk \&process, \%hash; 

Để có giải pháp toàn diện hơn (nhưng khó tìm hơn ở CPAN), hãy sử dụng Data::Visitor::Callback hoặc mô-đun chính của nó - điều này có lợi thế giúp bạn kiểm soát tốt hơn những gì bạn làm và (chỉ dành cho tín dụng đường phố bổ sung) được viết bằng Moose.

+0

Tôi có cùng một vấn đề với mô-đun này mà tôi có với Tệp :: Tìm: "... hàm & muốn là một cuộc gọi lại chung chung và không cho biết Dữ liệu :: Đi bộ nếu một mục là 'muốn' hay không. giá trị trả lại của nó bị bỏ qua. " Tại sao không để tôi quyết định có nên giảm giá trị băm không? – Axeman

+1

Bạn có thể lọc danh sách các thứ cần xử lý với tùy chọn preprocess => sub {} arg - xem tài liệu để biết thêm chi tiết. – Penfold

Các vấn đề liên quan