วิธีใดที่ปลอดภัยที่สุดในการทำซ้ำผ่านคีย์ของแฮช Perl?


107

หากฉันมีแฮช Perl ที่มีคู่ (คีย์, ค่า) จำนวนมากฉันควรใช้วิธีใดในการวนซ้ำผ่านคีย์ทั้งหมด ฉันเคยได้ยินมาว่าการใช้eachอาจมีผลข้างเคียงที่ไม่ได้ตั้งใจในทางใดทางหนึ่ง เป็นเช่นนั้นจริงหรือไม่และเป็นหนึ่งในสองวิธีต่อไปนี้ที่ดีที่สุดหรือมีวิธีที่ดีกว่านี้?

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

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

คำตอบ:


199

หลักการทั่วไปคือการใช้ฟังก์ชันที่เหมาะสมกับความต้องการของคุณมากที่สุด

หากคุณต้องการเพียงแค่คีย์และไม่ได้วางแผนที่จะอ่านค่าใด ๆ ให้ใช้คีย์ ():

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

หากคุณต้องการเพียงแค่ค่าให้ใช้ค่า ():

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

หากคุณต้องการคีย์และค่าให้ใช้แต่ละ ():

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

หากคุณวางแผนที่จะเปลี่ยนคีย์ของแฮชด้วยวิธีใดก็ตามยกเว้นการลบคีย์ปัจจุบันในระหว่างการทำซ้ำคุณจะต้องไม่ใช้แต่ละคีย์ () ตัวอย่างเช่นรหัสนี้เพื่อสร้างชุดคีย์ตัวพิมพ์ใหญ่ใหม่ที่มีค่าสองเท่าทำงานได้ดีโดยใช้คีย์ ():

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

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

การผลิตแฮชผลลัพธ์ที่คาดหวัง:

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

แต่ใช้แต่ละ () เพื่อทำสิ่งเดียวกัน:

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

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

ให้ผลลัพธ์ที่ไม่ถูกต้องในรูปแบบที่คาดเดาได้ยาก ตัวอย่างเช่น:

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

อย่างไรก็ตามสิ่งนี้ปลอดภัย:

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

ทั้งหมดนี้มีอธิบายไว้ในเอกสาร perl:

% perldoc -f keys
% perldoc -f each

6
โปรดเพิ่มคีย์ void-context% h; ก่อนแต่ละลูปเพื่อแสดงอย่างปลอดภัยโดยใช้ตัววนซ้ำ
ysth

5
มีข้อแม้อื่นด้วย ตัววนซ้ำจะถูกผูกไว้กับแฮชไม่ใช่บริบทซึ่งหมายความว่าจะไม่มีการป้อนซ้ำ ตัวอย่างเช่นหากคุณวนซ้ำแฮชและพิมพ์แฮช perl จะรีเซ็ตตัววนซ้ำภายในทำให้โค้ดนี้วนซ้ำไม่รู้จบ: my% hash = (a => 1, b => 2, c => 3,); ในขณะที่ (($ k, $ v) = แต่ละ% แฮช) {พิมพ์% แฮช; } อ่านเพิ่มเติมได้ที่blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler

28

สิ่งหนึ่งที่คุณควรระวังเมื่อใช้eachคือมันมีผลข้างเคียงจากการเพิ่ม "สถานะ" ในแฮชของคุณ (แฮชต้องจำไว้ว่าคีย์ "ถัดไป" คืออะไร) เมื่อใช้โค้ดเช่นเดียวกับตัวอย่างที่โพสต์ไว้ด้านบนซึ่งทำซ้ำแฮชทั้งหมดในครั้งเดียวโดยปกติจะไม่เป็นปัญหา อย่างไรก็ตามคุณจะพบปัญหาอย่างหนักในการติดตาม (ฉันพูดจากประสบการณ์;) เมื่อใช้eachร่วมกับคำสั่งเช่น lastหรือreturnเพื่อออกจากwhile ... eachลูปก่อนที่คุณจะประมวลผลคีย์ทั้งหมด

ในกรณีนี้แฮชจะจดจำคีย์ที่ส่งคืนไปแล้วและเมื่อคุณใช้eachในครั้งต่อไป (อาจอยู่ในโค้ดส่วนที่ไม่เกี่ยวข้องทั้งหมด) ก็จะดำเนินการต่อที่ตำแหน่งนี้

ตัวอย่าง:

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

สิ่งนี้พิมพ์:

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

เกิดอะไรขึ้นกับคีย์ "bar" และ baz "พวกเขายังคงอยู่ที่นั่น แต่ครั้งที่สองeachเริ่มจากจุดที่อันแรกค้างไว้และหยุดเมื่อถึงจุดสิ้นสุดของแฮชดังนั้นเราจึงไม่เห็นมันในลูปที่สอง


22

สถานที่ที่eachสามารถทำให้คุณเกิดปัญหาได้คือมันเป็นตัวทำซ้ำที่แท้จริงและไม่กำหนดขอบเขต โดยตัวอย่าง:

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

หากคุณต้องการให้แน่ใจว่าeachได้รับคีย์และค่าทั้งหมดคุณต้องแน่ใจว่าคุณใช้keysหรือvaluesก่อน (เนื่องจากจะรีเซ็ตตัววนซ้ำ) ดูเอกสารสำหรับแต่ละรายการ


14

การใช้ไวยากรณ์แต่ละชุดจะป้องกันไม่ให้สร้างชุดคีย์ทั้งหมดพร้อมกัน สิ่งนี้อาจมีความสำคัญหากคุณใช้แฮชแบบผูกกับฐานข้อมูลที่มีแถวนับล้าน คุณไม่ต้องการสร้างรายการคีย์ทั้งหมดพร้อมกันและใช้หน่วยความจำกายภาพของคุณหมด ในกรณีนี้แต่ละตัวทำหน้าที่เป็นตัววนซ้ำในขณะที่คีย์จะสร้างอาร์เรย์ทั้งหมดก่อนที่ลูปจะเริ่ม

ดังนั้นสถานที่เดียวที่ "แต่ละ" จะใช้งานได้จริงคือเมื่อแฮชมีขนาดใหญ่มาก (เทียบกับหน่วยความจำที่มี) สิ่งนี้มีแนวโน้มที่จะเกิดขึ้นเฉพาะเมื่อแฮชเองไม่อยู่ในหน่วยความจำเองเว้นแต่คุณจะตั้งโปรแกรมอุปกรณ์รวบรวมข้อมูลแบบใช้มือถือหรือสิ่งที่มีหน่วยความจำขนาดเล็ก

หากหน่วยความจำไม่เป็นปัญหาโดยปกติแล้วกระบวนทัศน์แผนที่หรือคีย์จะเป็นสิ่งที่เหนือกว่าและง่ายต่อการอ่านกระบวนทัศน์


6

ความคิดเบ็ดเตล็ดบางส่วนในหัวข้อนี้:

  1. ไม่มีสิ่งใดที่ไม่ปลอดภัยเกี่ยวกับตัวทำซ้ำแฮชใด ๆ สิ่งที่ไม่ปลอดภัยคือการแก้ไขคีย์ของแฮชในขณะที่คุณกำลังทำซ้ำ (การแก้ไขค่านี้ปลอดภัยอย่างสมบูรณ์แบบ) ผลข้างเคียงที่เป็นไปได้เพียงอย่างเดียวที่ฉันคิดได้คือvaluesส่งคืนนามแฝงซึ่งหมายความว่าการแก้ไขจะแก้ไขเนื้อหาของแฮช นี่คือการออกแบบ แต่อาจไม่ใช่สิ่งที่คุณต้องการในบางสถานการณ์
  2. คำตอบที่เป็นที่ยอมรับของ John นั้นดีโดยมีข้อยกเว้นประการหนึ่งคือเอกสารประกอบชัดเจนว่าไม่ปลอดภัยที่จะเพิ่มคีย์ในขณะที่ทำซ้ำผ่านแฮช อาจใช้ได้กับชุดข้อมูลบางชุด แต่จะล้มเหลวสำหรับชุดอื่น ๆ ขึ้นอยู่กับลำดับการแฮช
  3. ตามที่ระบุไว้แล้วว่าสามารถลบคีย์สุดท้ายที่ส่งคืนeachได้อย่างปลอดภัย นี่ไม่เป็นความจริงสำหรับkeysas eachis an iterator ในขณะที่keysส่งคืนรายการ

2
Re "ไม่เป็นความจริงสำหรับคีย์" แทนที่จะใช้กับคีย์และการลบใด ๆ ก็ปลอดภัย วลีที่คุณใช้บอกเป็นนัยว่าไม่ปลอดภัยที่จะลบอะไรเลยเมื่อใช้กุญแจ
ysth

2
Re: "ไม่มีสิ่งใดที่ไม่ปลอดภัยเกี่ยวกับตัวทำซ้ำแฮชใด ๆ " อันตรายอื่น ๆ คือสมมติว่าตัววนซ้ำอยู่ที่จุดเริ่มต้นก่อนที่จะเริ่มแต่ละลูปตามที่คนอื่นพูดถึง
ysth

3

ฉันมักจะใช้วิธีที่ 2 เช่นกัน ประโยชน์เพียงอย่างเดียวของการใช้แต่ละรายการคือหากคุณเพียงแค่อ่าน (แทนที่จะกำหนดค่าใหม่) ค่าของรายการแฮคุณจะไม่อ้างถึงแฮชอย่างต่อเนื่อง


3

ผมอาจจะโดนเจ้านี้กัด แต่คิดว่าเป็นความชอบส่วนบุคคล ฉันไม่พบการอ้างอิงใด ๆ ในเอกสารสำหรับแต่ละ () ที่แตกต่างจากคีย์ () หรือค่า () (นอกเหนือจากคำตอบที่ชัดเจนว่า "ส่งคืนสิ่งที่แตกต่างกัน" อันที่จริงเอกสารระบุว่าใช้ตัวทำซ้ำเดียวกันและทั้งหมด คืนค่ารายการจริงแทนที่จะเป็นสำเนาของค่าเหล่านี้และการแก้ไขแฮชในขณะที่ทำซ้ำโดยใช้การเรียกใด ๆ นั้นไม่ดี

ทั้งหมดที่กล่าวมาฉันมักจะใช้คีย์ () เกือบตลอดเวลาเพราะสำหรับฉันแล้วโดยปกติแล้วการบันทึกด้วยตนเองมากกว่าเพื่อเข้าถึงค่าของคีย์ผ่านแฮชเอง ฉันใช้ค่า () เป็นครั้งคราวเมื่อค่าเป็นการอ้างอิงถึงโครงสร้างขนาดใหญ่และคีย์ของแฮชได้ถูกเก็บไว้ในโครงสร้างแล้วซึ่งเมื่อถึงจุดนั้นคีย์จะซ้ำซ้อนและฉันไม่ต้องการมัน ฉันคิดว่าฉันใช้แต่ละ () 2 ครั้งใน 10 ปีของการเขียนโปรแกรม Perl และอาจเป็นตัวเลือกที่ผิดทั้งสองครั้ง =)


2

ฉันมักจะใช้keysและฉันไม่สามารถนึกถึงครั้งสุดท้ายที่ฉันใช้หรืออ่านการใช้each.

อย่าลืมmapขึ้นอยู่กับว่าคุณกำลังทำอะไรอยู่!

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

6
อย่าใช้แผนที่เว้นแต่คุณต้องการค่าที่ส่งคืน
ko-dos

-1

ฉันพูดว่า:

  1. ใช้สิ่งที่ง่ายที่สุดในการอ่าน / ทำความเข้าใจสำหรับคนส่วนใหญ่ (ดังนั้นคีย์โดยปกติฉันจะเถียง)
  2. ใช้สิ่งที่คุณตัดสินใจอย่างสม่ำเสมอตลอดทั้งฐานรหัสทั้งหมด

สิ่งนี้ให้ข้อดี 2 ประการ:

  1. การมองเห็นโค้ด "ทั่วไป" นั้นง่ายกว่าดังนั้นคุณสามารถแยกตัวประกอบฟังก์ชัน / เมธิไดโอดได้
  2. ผู้พัฒนาในอนาคตจะดูแลรักษาได้ง่ายขึ้น

ฉันไม่คิดว่าการใช้คีย์แต่ละอันมีราคาแพงกว่าดังนั้นจึงไม่จำเป็นต้องใช้โครงสร้างที่แตกต่างกันสองแบบสำหรับสิ่งเดียวกันในโค้ดของคุณ


1
ด้วยการเพิ่มขึ้นของการใช้งานหน่วยความจำโดยkeys hash-size * avg-key-sizeระบุว่าขนาดของคีย์จะถูก จำกัด ด้วยหน่วยความจำ (ขณะที่พวกเขากำลังเพียงองค์ประกอบมากมายเช่น "ของ" ค่าที่สอดคล้องกันภายใต้ประทุน) ในบางสถานการณ์ก็สามารถสาหัสราคาแพงมากขึ้นทั้งในการใช้งานหน่วยความจำและเวลาที่จะทำให้การคัดลอก
Adrian Günter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.