Apa cara teraman untuk melakukan iterasi melalui kunci hash Perl?

107

Jika saya memiliki hash Perl dengan sekumpulan pasangan (kunci, nilai), apa metode yang disukai untuk mengulang melalui semua kunci? Saya telah mendengar bahwa menggunakan eachmungkin dalam beberapa cara memiliki efek samping yang tidak diinginkan. Jadi, apakah itu benar, dan apakah salah satu dari dua metode berikut ini yang terbaik, atau adakah cara yang lebih baik?

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

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

Jawaban:

199

Aturan praktisnya adalah menggunakan fungsi yang paling sesuai dengan kebutuhan Anda.

Jika Anda hanya menginginkan kunci dan tidak berencana untuk membaca nilai apa pun, gunakan keys ():

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

Jika Anda hanya ingin nilainya, gunakan values ​​():

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

Jika Anda membutuhkan kunci dan nilainya, gunakan each ():

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

Jika Anda berencana untuk mengubah kunci hash dengan cara apapun kecuali untuk menghapus kunci saat ini selama iterasi, maka Anda tidak boleh menggunakan each (). Misalnya, kode ini untuk membuat satu set baru kunci huruf besar dengan nilai ganda berfungsi dengan baik menggunakan keys ():

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

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

menghasilkan hash yang diharapkan:

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

Tetapi menggunakan each () untuk melakukan hal yang sama:

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

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

menghasilkan hasil yang salah dengan cara yang sulit diprediksi. Sebagai contoh:

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

Namun, ini aman:

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

Semua ini dijelaskan dalam dokumentasi perl:

% perldoc -f keys
% perldoc -f each
John Siracusa
sumber
6
Harap tambahkan kunci konteks kosong% h; sebelum setiap loop untuk ditampilkan dengan aman menggunakan iterator.
ysth
5
Ada peringatan lain dengan masing-masing. Iterator terikat pada hash, bukan konteksnya, yang berarti bukan re-entrant. Misalnya jika Anda mengulang hash, dan mencetak hash perl akan mereset iterator secara internal, membuat loop kode ini tanpa henti: my% hash = (a => 1, b => 2, c => 3,); sementara (my ($ k, $ v) = setiap% hash) {print% hash; } Baca selengkapnya di blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler
28

Satu hal yang harus Anda waspadai saat menggunakan eachadalah bahwa ia memiliki efek samping menambahkan "status" ke hash Anda (hash harus mengingat kunci "berikutnya"). Saat menggunakan kode seperti cuplikan yang diposting di atas, yang mengulang seluruh hash sekaligus, ini biasanya tidak menjadi masalah. Namun, Anda akan mengalami kesulitan untuk melacak masalah (saya berbicara dari pengalaman;), ketika menggunakan eachbersama dengan pernyataan suka lastatau returnkeluar dari while ... eachloop sebelum Anda memproses semua kunci.

Dalam kasus ini, hash akan mengingat kunci mana yang telah dikembalikan, dan ketika Anda menggunakannya eachdi lain waktu (mungkin dalam bagian kode yang sama sekali tidak terkait), hash akan berlanjut pada posisi ini.

Contoh:

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

Ini mencetak:

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

Apa yang terjadi dengan kunci "bar" dan baz "? Mereka masih ada, tapi yang kedua eachdimulai dari yang pertama berhenti, dan berhenti ketika mencapai akhir hash, jadi kita tidak pernah melihatnya di loop kedua.

8jean
sumber
22

Tempat di mana eachdapat menyebabkan masalah bagi Anda adalah bahwa ini adalah iterator non-cakupan yang benar. Sebagai contoh:

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

Jika Anda perlu memastikan bahwa eachmendapatkan semua kunci dan nilai, Anda perlu memastikan Anda menggunakan keysatau valuesterlebih dahulu (karena itu menyetel ulang iterator). Lihat dokumentasi untuk masing-masing .

Darren Meyer
sumber
14

Menggunakan setiap sintaks akan mencegah seluruh rangkaian kunci dibuat sekaligus. Ini bisa menjadi penting jika Anda menggunakan hash terkait ke database dengan jutaan baris. Anda tidak ingin membuat seluruh daftar kunci sekaligus dan menghabiskan memori fisik Anda. Dalam hal ini masing-masing berfungsi sebagai iterator sedangkan kunci sebenarnya menghasilkan seluruh array sebelum loop dimulai.

Jadi, satu-satunya tempat "setiap" yang benar-benar digunakan adalah saat hash sangat besar (dibandingkan dengan memori yang tersedia). Itu hanya mungkin terjadi ketika hash itu sendiri tidak tinggal di memori itu sendiri kecuali Anda memprogram perangkat pengumpulan data genggam atau sesuatu dengan memori kecil.

Jika memori tidak menjadi masalah, biasanya paradigma peta atau kunci adalah paradigma yang lebih umum dan lebih mudah dibaca.


sumber
6

Beberapa pemikiran lain tentang topik ini:

  1. Tidak ada yang tidak aman tentang iterator hash itu sendiri. Yang tidak aman adalah memodifikasi kunci hash saat Anda mengulanginya. (Sangat aman untuk mengubah nilai.) Satu-satunya efek samping potensial yang dapat saya pikirkan adalah valuesmengembalikan alias yang berarti bahwa memodifikasinya akan mengubah konten hash. Ini memang disengaja tetapi mungkin bukan yang Anda inginkan dalam beberapa keadaan.
  2. Jawaban yang diterima John bagus dengan satu pengecualian: dokumentasinya jelas bahwa tidak aman untuk menambahkan kunci saat melakukan iterasi melalui hash. Ini mungkin berfungsi untuk beberapa kumpulan data tetapi akan gagal untuk yang lain tergantung pada urutan hash.
  3. Seperti yang telah disebutkan, aman untuk menghapus kunci terakhir yang dikembalikan oleh each. Ini tidak benar karena keyssebagai eachiterator saat keysmengembalikan daftar.
Michael Carman
sumber
2
Re "tidak benar untuk kunci", sebaliknya: ini tidak berlaku untuk kunci dan penghapusan apa pun aman. Ungkapan yang Anda gunakan menyiratkan bahwa tidak pernah aman untuk menghapus apa pun saat menggunakan kunci.
ysth
2
Re: "tidak ada yang tidak aman tentang salah satu iterator hash", bahaya lainnya adalah mengasumsikan iterator ada di awal sebelum memulai setiap loop, seperti yang disebutkan orang lain.
ysth
3

Saya selalu menggunakan metode 2 juga. Satu-satunya manfaat menggunakan masing-masing adalah jika Anda hanya membaca (daripada menetapkan ulang) nilai entri hash, Anda tidak terus-menerus membatalkan referensi hash.

jaredg
sumber
3

Saya mungkin akan digigit oleh yang satu ini tetapi saya pikir itu adalah preferensi pribadi. Saya tidak dapat menemukan referensi apa pun di dokumen untuk setiap () berbeda dari kunci () atau nilai () (selain jawaban yang jelas "mereka mengembalikan hal yang berbeda". Faktanya, dokumen menyatakan penggunaan iterator yang sama dan semuanya mengembalikan nilai daftar sebenarnya alih-alih menyalinnya, dan bahwa memodifikasi hash saat mengulanginya menggunakan panggilan apa pun itu buruk.

Semua yang dikatakan, saya hampir selalu menggunakan keys () karena bagi saya biasanya lebih mendokumentasikan diri untuk mengakses nilai kunci melalui hash itu sendiri. Saya kadang-kadang menggunakan values ​​() ketika nilainya adalah referensi ke struktur besar dan kunci hash sudah disimpan dalam struktur, pada titik mana kuncinya berlebihan dan saya tidak membutuhkannya. Saya pikir saya telah menggunakan masing-masing () 2 kali dalam 10 tahun pemrograman Perl dan itu mungkin pilihan yang salah keduanya =)

jj33
sumber
2

Saya biasanya menggunakan keysdan saya tidak dapat memikirkan kapan terakhir kali saya menggunakan atau membaca penggunaan each.

Jangan lupa map, tergantung pada apa yang Anda lakukan dalam loop!

map { print "$_ => $hash{$_}\n" } keys %hash;
Gary Richardson
sumber
6
jangan gunakan peta kecuali Anda menginginkan nilai yang dikembalikan
ko-dos
-1

Saya akan mengatakan:

  1. Gunakan apa pun yang paling mudah dibaca / dipahami bagi kebanyakan orang (jadi kuncinya, biasanya, menurut saya)
  2. Gunakan apa pun yang Anda putuskan secara konsisten melalui seluruh basis kode.

Ini memberikan 2 keuntungan utama:

  1. Lebih mudah untuk menemukan kode "umum" sehingga Anda dapat memfaktorkan ulang menjadi fungsi / methiods.
  2. Lebih mudah bagi pengembang masa depan untuk memeliharanya.

Menurut saya tidak lebih mahal menggunakan kunci untuk masing-masing, jadi tidak perlu dua konstruksi berbeda untuk hal yang sama dalam kode Anda.

Hogsmill
sumber
1
Dengan keyspenggunaan memori meningkat hash-size * avg-key-size. Mengingat bahwa ukuran kunci hanya dibatasi oleh memori (karena mereka hanya elemen larik seperti nilai yang sesuai "mereka" di bawah tenda), dalam beberapa situasi, ini bisa menjadi sangat mahal baik dalam penggunaan memori dan waktu yang dibutuhkan untuk membuat salinan.
Adrian Günter