2008-08-06 36 views
80

Nếu tôi có một băm Perl với một loạt các cặp (khóa, giá trị), phương pháp ưa thích của việc lặp qua tất cả các khóa là gì? Tôi đã nghe nói rằng sử dụng each có thể theo một cách nào đó có tác dụng phụ ngoài ý muốn. Vì vậy, đó là sự thật, và là một trong hai phương pháp sau tốt nhất, hoặc là có một cách tốt hơn?Cách an toàn nhất để lặp qua các khóa của mã băm Perl là gì?

# Method 1 
while (my ($key, $value) = each(%hash)) { 
    # Something 
} 

# Method 2 
foreach my $key (keys(%hash)) { 
    # Something 
} 

Trả lời

161

Quy tắc chung là sử dụng chức năng phù hợp nhất với nhu cầu của bạn.

Nếu bạn chỉ muốn các phím và không có kế hoạch từng đọc bất kỳ giá trị, sử dụng các phím():

foreach my $key (keys %hash) { ... } 

Nếu bạn chỉ muốn các giá trị, giá trị sử dụng():

foreach my $val (values %hash) { ... } 

Nếu bạn cần các phím các giá trị, sử dụng mỗi():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop 
while(my($k, $v) = each %hash) { ... } 

Nếu bạn định thay đổi các khóa của băm theo bất kỳ cách nào trừ để xóa khóa hiện tại trong khi lặp lại, thì bạn không được sử dụng từng(). Ví dụ, đoạn mã này để tạo ra một bộ mới của các phím chữ hoa với giá trị tăng gấp đôi hoạt động tốt sử dụng các phím():

%h = (a => 1, b => 2); 

foreach my $k (keys %h) 
{ 
    $h{uc $k} = $h{$k} * 2; 
} 

sản xuất dự kiến ​​băm kết quả:

(a => 1, A => 2, b => 2, B => 4) 

Nhưng sử dụng mỗi() để thực hiện cùng một điều:

%h = (a => 1, b => 2); 

keys %h; 
while(my($k, $v) = each %h) 
{ 
    $h{uc $k} = $h{$k} * 2; # BAD IDEA! 
} 

tạo kết quả không chính xác theo cách khó dự đoán.Ví dụ:

(a => 1, A => 2, b => 2, B => 8) 

này, tuy nhiên, là an toàn:

keys %h; 
while(my($k, $v) = each %h) 
{ 
    if(...) 
    { 
    delete $h{$k}; # This is safe 
    } 
} 

Tất cả điều này được mô tả trong tài liệu perl:

% perldoc -f keys 
% perldoc -f each 
+6

Vui lòng thêm một khóa void-context% h; trước mỗi vòng lặp để hiển thị một cách an toàn bằng cách sử dụng trình lặp. – ysth

+5

Có một cảnh báo trước khác. Trình lặp được ràng buộc với hàm băm, không phải là bối cảnh, có nghĩa là nó không được tái nhập. Ví dụ, nếu bạn lặp qua một băm, và in giá trị băm perl sẽ thiết lập lại bộ lặp, làm cho mã này lặp vô tận: % băm của tôi = (a => 1, b => 2, c => 3,); trong khi (của tôi ($ k, $ v) = mỗi băm%) { in% băm; } Đọc thêm tại http://blogs.perl.org/users/rurban/2014/04/do-not-use-each.html – Rawler

3

Tôi có thể bị cắn bởi điều này nhưng tôi nghĩ rằng đó là sở thích cá nhân. Tôi không thể tìm thấy bất kỳ tham chiếu nào trong tài liệu cho mỗi() khác với các phím() hoặc các giá trị() (khác với câu trả lời "chúng trả về những thứ khác nhau" rõ ràng. Trong thực tế, các tài liệu cho biết sử dụng cùng một trình lặp và tất cả chúng đều trả về các giá trị danh sách thực tế thay vì các bản sao của chúng, và việc sửa đổi băm trong khi lặp qua nó bằng cách sử dụng bất kỳ cuộc gọi nào là xấu.

Tất cả điều đó nói rằng tôi thường sử dụng nhiều tài liệu để Tôi thỉnh thoảng sử dụng giá trị() khi giá trị là một tham chiếu đến một cấu trúc lớn và khóa để băm đã được lưu trữ trong cấu trúc, tại thời điểm đó, khóa là dư thừa và tôi không Tôi nghĩ rằng tôi đã sử dụng mỗi() 2 lần trong 10 năm lập trình Perl và nó có lẽ là sự lựa chọn sai cả hai lần =)

4

Tôi luôn sử dụng phương pháp 2. Lợi ích duy nhất của việc sử dụng mỗi cái là nếu bạn chỉ đọc (thay vì gán lại) giá trị của mục nhập băm, bạn không liên tục bỏ tham chiếu đến băm.

1

Tôi thường sử dụng keys và tôi không thể nghĩ về lần cuối cùng tôi sử dụng hoặc đọc sử dụng each.

Đừng quên khoảng map, tùy thuộc vào những gì bạn đang làm trong vòng lặp!

map { print "$_ => $hash{$_}\n" } keys %hash; 
+4

không sử dụng bản đồ trừ khi bạn muốn giá trị trả lại –

12

Sử dụng từng cú pháp sẽ ngăn toàn bộ các khóa được tạo cùng một lúc. Điều này có thể quan trọng nếu bạn đang sử dụng băm tie-ed vào cơ sở dữ liệu với hàng triệu hàng. Bạn không muốn tạo toàn bộ danh sách các khóa cùng một lúc và làm cạn kiệt bộ nhớ vật lý của bạn. Trong trường hợp này, mỗi bản ghi là một trình lặp trong khi các khóa thực sự tạo ra toàn bộ mảng trước khi vòng lặp bắt đầu.

Vì vậy, nơi duy nhất "mỗi" là sử dụng thực tế là khi băm là rất lớn (so với bộ nhớ có sẵn). Điều đó chỉ có khả năng xảy ra khi bản thân băm không tồn tại trong chính bộ nhớ trừ khi bạn đang lập trình một thiết bị thu thập dữ liệu cầm tay hoặc một thứ gì đó có bộ nhớ nhỏ.

Nếu bộ nhớ không phải là vấn đề, thường thì mô hình bản đồ hoặc phím là mô hình dễ đọc và dễ đọc hơn.

4

Một vài suy nghĩ linh tinh về chủ đề này:

  1. Không có gì không an toàn về bất cứ băm lặp phải là họ. Những gì là không an toàn là sửa đổi các phím của một băm trong khi bạn đang lặp qua nó. (Nó hoàn toàn an toàn để sửa đổi các giá trị.) Tác dụng phụ tiềm ẩn duy nhất tôi có thể nghĩ là values trả về các bí danh có nghĩa là sửa đổi chúng sẽ sửa đổi nội dung của băm. Điều này là do thiết kế nhưng có thể không phải là những gì bạn muốn trong một số trường hợp.
  2. John's accepted answer là tốt với một ngoại lệ: tài liệu rõ ràng là không an toàn khi thêm khóa khi lặp qua băm. Nó có thể làm việc cho một số bộ dữ liệu nhưng sẽ thất bại cho những người khác tùy thuộc vào thứ tự băm.
  3. Như đã lưu ý, an toàn để xóa khóa cuối cùng được trả về each. Đây là không đúng cho keyseach là một trình lặp trong khi keys trả về một danh sách.
+2

Re "not đúng đối với khóa ", thay vào đó: nó không áp dụng cho các phím và mọi xóa đều an toàn. Phrasing bạn sử dụng ngụ ý nó không bao giờ an toàn để xóa bất cứ điều gì khi sử dụng các phím. – ysth

+2

Re: "không có gì không an toàn về bất kỳ của iterators băm", nguy hiểm khác là giả định iterator là lúc bắt đầu trước khi bắt đầu một vòng lặp, như những người khác đề cập đến. – ysth

20

Một điều bạn nên biết khi sử dụng each là nó có tác dụng phụ của việc thêm "nhà nước" để băm của bạn (hash có nhớ gì "bên cạnh" Điều quan trọng là). Khi sử dụng mã như các đoạn mã được đăng ở trên, lặp lại toàn bộ băm trong một lần, điều này thường không phải là vấn đề . Tuy nhiên, bạn sẽ gặp khó khăn trong việc theo dõi các vấn đề (tôi nói từ kinh nghiệm ;), khi sử dụng each cùng với các tuyên bố như last hoặc return để thoát khỏi vòng while ... each trước khi bạn xử lý tất cả các khóa.

Trong trường hợp này, các hash sẽ nhớ mà phím nó đã trở lại, và khi bạn sử dụng each vào nó lần sau (có thể trong một mảnh totaly không liên quan code), nó sẽ tiếp tục tại vị trí này.

Ví dụ:

my %hash = (foo => 1, bar => 2, baz => 3, quux => 4); 

# find key 'baz' 
while (my ($k, $v) = each %hash) { 
    print "found key $k\n"; 
    last if $k eq 'baz'; # found it! 
} 

# later ... 

print "the hash contains:\n"; 

# iterate over all keys: 
while (my ($k, $v) = each %hash) { 
    print "$k => $v\n"; 
} 

in này:

found key bar 
found key baz 
the hash contains: 
quux => 4 
foo => 1 

gì đã xảy ra với các phím "thanh" và baz "Họ vẫn còn đó, nhưng thứ hai each bắt đầu nơi người đầu tiên? còn lại, và dừng lại khi nó đạt đến cuối của băm, vì vậy chúng tôi không bao giờ nhìn thấy chúng trong vòng lặp thứ hai.

18

Nơi mà each có thể gây ra cho bạn các vấn đề là nó là một trình lặp thực sự, không có phạm vi. Bằng cách ví dụ:

while (my ($key,$val) = each %a_hash) { 
    print "$key => $val\n"; 
    last if $val; #exits loop when $val is true 
} 

# but "each" hasn't reset!! 
while (my ($key,$val) = each %a_hash) { 
    # continues where the last loop left off 
    print "$key => $val\n"; 
} 

Nếu bạn cần phải chắc chắn rằng each được tất cả các phím và các giá trị, bạn cần phải chắc chắn rằng bạn sử dụng keys hoặc values đầu tiên (như reset iterator). Xem số documentation for each.

+0

Điều này có thể bị cắn trong $$ nếu không cẩn thận – sdkks

-2

tôi woudl nói:

  1. Sử dụng bất cứ điều gì là đơn giản nhất để đọc/hiểu cho hầu hết mọi người (vì vậy chìa khóa, thường, tôi muốn tranh luận)
  2. Sử dụng bất cứ điều gì bạn quyết định một cách nhất quán nghĩ các cơ sở mã chung.

này cung cấp cho 2 lợi thế lớn:

  1. Nó dễ dàng hơn để phát hiện "chung" mã để bạn có thể tái yếu tố thành các hàm/methiods.
  2. Các nhà phát triển trong tương lai sẽ dễ dàng hơn trong việc duy trì.

Tôi không nghĩ rằng việc sử dụng các khóa trên mỗi thẻ sẽ đắt hơn, vì vậy không cần hai cấu trúc khác nhau cho cùng một thứ trong mã của bạn.

+0

Khi mức sử dụng bộ nhớ 'phím' tăng lên bằng' kích thước băm * kích thước khóa -gg '.Cho rằng kích thước khóa chỉ bị giới hạn bởi bộ nhớ (vì chúng chỉ là các phần tử mảng giống như "giá trị tương ứng" của chúng trong mui xe), trong một số trường hợp, nó có thể * tốn kém * tốn kém hơn trong việc sử dụng bộ nhớ và thời gian thực hiện để sao chép . –

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