Perl hash anahtarları aracılığıyla yinelemenin en güvenli yolu nedir?


107

Bir grup (anahtar, değer) çifti içeren bir Perl hash'ım varsa, tüm anahtarları yinelemek için tercih edilen yöntem nedir? Kullanmanın eachbir şekilde istenmeyen yan etkileri olabileceğini duydum . Öyleyse, bu doğru mu ve aşağıdaki iki yöntemden biri en iyisi mi yoksa daha iyi bir yolu var mı?

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

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

Yanıtlar:


199

Temel kural, ihtiyaçlarınıza en uygun işlevi kullanmaktır.

Yalnızca anahtarları istiyorsanız ve hiçbir değeri okumayı planlamıyorsanız , keys () kullanın:

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

Yalnızca değerleri istiyorsanız, değerleri kullanın ():

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

Anahtarlara ve değerlere ihtiyacınız varsa her birini () kullanın:

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

Yineleme sırasında mevcut anahtarı silmek dışında herhangi bir şekilde hash anahtarlarını değiştirmeyi planlıyorsanız , each () kullanmamalısınız. Örneğin, değerleri iki katına çıkaran yeni bir büyük harfli anahtarlar kümesi oluşturmak için kullanılan bu kod, () tuşları kullanıldığında iyi çalışır:

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

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

beklenen sonuç hash'i üretmek:

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

Ancak aynı şeyi yapmak için each () kullanmak:

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

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

tahmin edilmesi zor şekillerde yanlış sonuçlar verir. Örneğin:

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

Ancak bu güvenlidir:

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

Bunların tümü perl belgelerinde açıklanmaktadır:

% perldoc -f keys
% perldoc -f each

6
Lütfen bir void bağlam anahtarı ekleyin% h; her döngüden önce yineleyiciyi kullanarak güvenle gösterebilirsiniz.
ysth

5
Her biri için başka bir uyarı var. Yineleyici, bağlama değil, hash'e bağlıdır, yani yeniden giren değildir. Örneğin, bir karma üzerinde döngü yaparsanız ve karma perl yazdırırsanız, yineleyiciyi dahili olarak sıfırlar ve bu kod döngüsünü sonsuz hale getirir: my% hash = (a => 1, b => 2, c => 3,); while (($ k, $ v) = her% hash) {print% hash; } Blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler

28

Kullanırken bilmeniz gereken bir şey each, hash'inize "durum" eklemenin yan etkisine sahip olmasıdır (karma, "sonraki" anahtarın ne olduğunu hatırlamalıdır). Yukarıda yayınlanan ve tek seferde tüm hash üzerinden yinelenen kodlar gibi kodu kullanırken, bu genellikle bir sorun değildir. Bununla birlikte, tüm anahtarları işlemeden önce döngüden çıkmak veya eachgibi ifadelerle birlikte kullandığınızda (deneyimlerime dayanarak konuşuyorum;) sorunları bulmakta zorlanacaksınız .lastreturnwhile ... each

Bu durumda, hash hangi anahtarları döndürdüğünü hatırlayacak ve eachonu bir dahaki sefere kullandığınızda (belki tamamen ilgisiz bir kod parçasında), bu konumda devam edecektir.

Misal:

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

Bu yazdırır:

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

"Bar" ve baz "anahtarlarına ne oldu? Hala oradalar, ancak ikincisi eachilkinin kaldığı yerden başlar ve hash'in sonuna ulaştığında durur, bu yüzden onları ikinci döngüde asla görmeyiz.


22

eachSize sorunlara neden olabilecek yer , bunun gerçek, kapsamlı olmayan bir yineleyici olmasıdır. Örnek olarak:

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

Bunun eachtüm anahtarları ve değerleri aldığından emin olmanız gerekiyorsa, önce keysveya kullandığınızdan emin olmanız gerekir values(bu yineleyiciyi sıfırlar). Her birinin belgelerine bakın .


14

Her sözdiziminin kullanılması, tüm anahtar kümesinin aynı anda oluşturulmasını engeller. Milyonlarca satır içeren bir veritabanına bağlı bir karma kullanıyorsanız bu önemli olabilir. Tüm anahtar listesini tek seferde oluşturmak ve fiziksel belleğinizi tüketmek istemezsiniz. Bu durumda her biri bir yineleyici görevi görür, oysa anahtarlar aslında döngü başlamadan önce dizinin tamamını oluşturur.

Bu nedenle, "her" in gerçek kullanımının olduğu tek yer, hash'in çok büyük olduğu zamandır (mevcut belleğe kıyasla). Elde tutulan bir veri toplama cihazını veya küçük hafızalı bir şeyi programlamadığınız sürece, bu sadece hash'in kendisi hafızada yaşamadığında gerçekleşebilir.

Bellek bir sorun değilse, genellikle harita veya anahtarlar paradigması daha baskın ve okuması kolay olan paradigmadır.


6

Bu konuyla ilgili birkaç farklı düşünce:

  1. Karma yineleyicilerin kendileri için güvenli olmayan hiçbir şey yoktur. Güvensiz olan şey, üzerinde yineleme yaparken bir karmanın anahtarlarını değiştirmektir. (Değerleri değiştirmek tamamen güvenlidir.) Aklıma gelen tek potansiyel yan etki şudur:values takma adları döndürmesidir onları değiştirmenin karmanın içeriğini değiştireceği anlamına gelir. Bu tasarım gereğidir, ancak bazı durumlarda istediğiniz şey olmayabilir.
  2. John'un kabul ettiği cevap , bir istisna dışında iyidir: Bir karma üzerinde yineleme yaparken anahtar eklemenin güvenli olmadığı belgelenmiştir. Bazı veri kümeleri için çalışabilir, ancak karma sıraya bağlı olarak diğerleri için başarısız olacaktır.
  3. Daha önce belirtildiği gibi, tarafından döndürülen son anahtarı silmek güvenlidir each. Bu değil için geçerli keysolduğu gibi eachbir yineleyici varken keysgetiri listesi.

2
Yeniden "anahtarlar için doğru değil", bunun yerine: anahtarlar için geçerli değildir ve silme işlemi güvenlidir. Kullandığınız ifade, anahtarları kullanırken hiçbir şeyi silmenin asla güvenli olmadığı anlamına gelir.
ysth

2
Re: "hash yineleyicilerinin hiçbiri hakkında güvensiz hiçbir şey yok", diğer tehlike, yineleyicinin, diğerlerinin de belirttiği gibi, her döngüyü başlatmadan önce başlangıçta olduğunu varsaymaktır.
ysth

3

Ben de her zaman 2. yöntemi kullanıyorum. Her birini kullanmanın tek yararı, karma girişin değerini yalnızca okuyorsanız (yeniden atamak yerine), karma değerinin sürekli olarak referansını kaldırmamanızdır.


3

Bu beni ısırabilir ama bunun kişisel bir tercih olduğunu düşünüyorum. Dokümanlarda, her birinin () anahtarlardan () veya değerlerden () farklı olduğuna ("farklı şeyler döndürdükleri" bariz yanıt dışında) herhangi bir referans bulamıyorum. Aslında, dokümanlar aynı yineleyiciyi kullanmayı belirtir ve hepsi bunların kopyaları yerine gerçek liste değerlerini döndürür ve herhangi bir çağrıyı kullanarak üzerinde yinelerken karmayı değiştirmek kötüdür.

Bütün bunlar, neredeyse her zaman anahtarları () kullanıyorum çünkü bana göre anahtarın değerine hash aracılığıyla erişmek daha çok kendi kendini belgelemektir. Değer büyük bir yapıya referans olduğunda ve hash anahtarı zaten yapıda depolandığında, bu noktada anahtar gereksiz olduğunda ve ona ihtiyacım olmadığında, bazen değerleri () kullanıyorum. Sanırım her birini () 10 yıllık Perl programlamasında 2 kez kullandım ve muhtemelen her iki seferde de yanlış seçim oldu =)


2

Genellikle kullanıyorum keysve en son ne zaman kullandığımı veya okuduğumu düşünemiyorum each.

Döngüde mapne yaptığınıza bağlı olarak unutma !

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

6
dönüş değerini istemediğiniz sürece haritayı kullanmayın
ko-dos

-1

Diyorum ki:

  1. Çoğu insan için okuması / anlaması en kolay olanı kullanın (bu nedenle anahtarlar, genellikle, tartışırım)
  2. Tüm kod tabanında tutarlı bir şekilde karar verdiğiniz şeyi kullanın.

Bu, 2 büyük avantaj sağlar:

  1. "Ortak" kodu tespit etmek daha kolaydır, böylece fonksiyonları / metotları yeniden faktörleyebilirsiniz.
  2. Gelecekteki geliştiricilerin bakımı daha kolay.

Her birinde anahtar kullanmanın daha pahalı olduğunu düşünmüyorum, bu nedenle kodunuzda aynı şey için iki farklı yapıya gerek yok.


1
İle keysbellek kullanımı artarhash-size * avg-key-size . Anahtar boyutunun yalnızca bellekle sınırlı olduğu göz önüne alındığında (yalnızca başlık altındaki "onların" karşılık gelen değerleri gibi dizi öğeleri olduklarından), bazı durumlarda hem bellek kullanımı hem de kopyalamanın yapılması için geçen süre açısından engelleyici bir şekilde daha pahalı olabilir .
Adrian Günter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.