Cách an toàn nhất để lặp lại các khóa của hàm băm Perl là gì?


107

Nếu tôi có một hàm băm Perl với một loạt các cặp (khóa, giá trị), thì phương pháp nào được ưa thích để lặp qua tất cả các khóa? Tôi nghe nói rằng sử dụng eachtheo một cách nào đó có thể có tác dụng phụ ngoài ý muốn. Vậy, điều đó có đúng không, và một trong hai phương pháp sau là tốt nhất, hay còn cách nào tốt hơn?

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

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

Câu trả lời:


199

Quy tắc ngón tay cái 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 khóa và không có kế hoạch đọc bất kỳ giá trị nào, hãy sử dụng các khóa ():

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

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

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

Nếu bạn cần các khóa giá trị, hãy sử dụng từng ():

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 hàm băm theo bất kỳ cách nào ngoại trừ việc xóa khóa hiện tại trong quá trình lặp, thì bạn không được sử dụng từng (). Ví dụ: mã này để tạo một bộ khóa chữ hoa mới với các giá trị nhân đôi hoạt động tốt bằng cách sử dụng các phím ():

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

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

tạo ra kết quả băm mong đợi:

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

Nhưng sử dụng mỗi () để làm điều tương tự:

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

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

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

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

Tuy nhiên, điều này là an toàn:

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

Tất cả những đ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 khóa ngữ cảnh void% h; trước mỗi vòng lặp để hiển thị an toàn bằng cách sử dụng trình vòng lặp.
ysth

5
Có một cảnh báo khác với mỗi. Trình lặp được liên kết với băm, không phải ngữ cảnh, có nghĩa là nó không tham gia lại. Ví dụ: nếu bạn lặp qua một mã băm và in mã băm perl sẽ đặt lại nội bộ trình lặp, làm cho vòng lặp mã này liên tục: my% hash = (a => 1, b => 2, c => 3,); while (my ($ k, $ v) = each% hash) {print% hash; } Đọc thêm tại blog.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler

28

Một điều bạn cần lưu ý khi sử dụng eachlà nó có tác dụng phụ là thêm "trạng thái" vào hàm băm của bạn (hàm băm phải nhớ phím "tiếp theo" là gì). Khi sử dụng mã như các đoạn mã được đăng ở trên, lặp lại toàn bộ hàm băm trong một lần, đây thường không phải là vấn đề. Tuy nhiên, bạn sẽ gặp phải các vấn đề khó theo dõi (tôi nói theo kinh nghiệm;), khi sử dụng eachcùng với các câu lệnh như lasthoặc returnđể thoát khỏi while ... eachvòng lặp trước khi bạn xử lý tất cả các khóa.

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

Thí 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";
}

Bản in này:

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

Điều gì đã xảy ra với các phím "bar" và baz "? Chúng vẫn ở đó, nhưng phím thứ hai eachbắt đầu từ nơi phím thứ nhất đã dừng lại và dừng lại khi đến cuối hàm băm, vì vậy chúng ta sẽ không bao giờ thấy chúng trong vòng lặp thứ hai.


22

Nơi eachcó thể gây ra vấn đề cho bạn là đó là một trình lặp đúng, không có phạm vi. Bằng cách lấy 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 chắc chắn rằng eachnhận được tất cả các khóa và giá trị, bạn cần đảm bảo rằng bạn sử dụng keyshoặcvalues trước (vì điều đó sẽ đặt lại trình lặp). Xem tài liệu cho từng loại .


14

Việc sử dụng từng cú pháp sẽ ngăn không cho toàn bộ bộ 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 hàm băm liên kết với cơ sở dữ liệu có hàng triệu hàng. Bạn không muốn tạo toàn bộ danh sách khóa cùng một lúc và làm cạn kiệt bộ nhớ vật lý của mình. Trong trường hợp này, mỗi khóa đóng vai trò là một trình vòng 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" được sử dụng thực sự là khi hàm băm 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 hàm băm không nằm trong bộ nhớ trừ khi bạn đang lập trình thiết bị thu thập dữ liệu cầm tay hoặc thiết bị có bộ nhớ nhỏ.

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


6

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 kỳ trình lặp băm nào. Điều không an toàn là sửa đổi các khóa của băm trong khi bạn đang lặp lại nó. (Hoàn toàn an toàn khi sửa đổi các giá trị.) Tác dụng phụ tiềm ẩn duy nhất mà tôi có thể nghĩ đến làvalues trả về bí danh có nghĩa là việc sửa đổi chúng sẽ sửa đổi nội dung của hàm 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 đã chấp nhận câu trả lời là tốt với một ngoại lệ: tài liệu rõ ràng rằng không an toàn khi thêm khóa trong khi lặp qua một hàm băm. Nó có thể hoạt động đối với một số bộ dữ liệu nhưng sẽ không thành công đối với những bộ khác tùy thuộc vào thứ tự băm.
  3. Như đã lưu ý, có thể an toàn khi xóa khóa cuối cùng được trả về each. Điều này không đúng đối keysvới eachmột trình lặp trong khi keystrả về một danh sách.

2
Re "không đúng với khóa", đúng hơn: nó không áp dụng cho khóa và mọi thao tác xóa đều an toàn. Phrasing bạn sử dụng ngụ ý rằng không bao giờ an toàn khi xóa bất kỳ thứ gì khi sử dụng khóa.
ysth

2
Re: "không có gì không an toàn về bất kỳ trình lặp băm nào", mối nguy hiểm khác là giả sử trình lặp ở đầu trước khi bắt đầu mỗi vòng lặp, như những người khác đề cập.
ysth

3

Tôi luôn 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 là nếu bạn chỉ đang đọc (thay vì chỉ định 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.


3

Tôi có thể bị cái này cắn 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 khóa () hoặc giá trị () (ngoài câu trả lời rõ ràng "chúng trả về những thứ khác nhau". Trên thực tế, 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ì bản sao của chúng và việc sửa đổi hàm băm trong khi lặp lại nó bằng bất kỳ lệnh gọi nào là không tốt.

Tất cả những gì đã nói, tôi hầu như luôn sử dụng các khóa () vì đối với tôi, việc truy cập giá trị của khóa thông qua chính hàm băm thường là tự ghi lại nhiều hơn. 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 của hàm băm đã được lưu trữ trong cấu trúc, tại thời điểm đó, khóa này là dư thừa và tôi không cần nó. Tôi nghĩ tôi đã sử dụng từng () 2 lần trong 10 năm lập trình Perl và có lẽ đó là lựa chọn sai cả hai lần =)


2

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

Đừng quên 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;

6
không sử dụng bản đồ, trừ khi bạn muốn giá trị trả về
ko-dos

-1

Tôi sẽ nói:

  1. Sử dụng bất cứ thứ gì dễ đọc / dễ hiểu nhất đối với hầu hết mọi người (vì vậy, thông thường, tôi sẽ 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 thông qua toàn bộ cơ sở mã.

Điều này mang lại 2 lợi thế chính:

  1. Việc phát hiện mã "phổ biến" dễ dàng hơn để bạn có thể phân tích lại thành các hàm / methiod.
  2. Nó dễ dàng hơn cho các nhà phát triển trong tương lai để bảo trì.

Tôi không nghĩ rằng việc sử dụng các khóa trên mỗi khóa 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.


1
Với keysviệc sử dụng bộ nhớ tăng lên hash-size * avg-key-size. Cho rằng kích thước quan trọng là chỉ bị giới hạn bởi bộ nhớ (như họ đang phần tử mảng giống như "họ" giá trị tương ứng dưới mui xe), trong một số trường hợp nó có thể ngăn cản đắt hơn ở cả hai sử dụng bộ nhớ và thời gian thực hiện để làm cho bản sao.
Adrian Günter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.