Vă mulțumim pentru susținere

Care este cel mai sigur mod de a itera prin cheile unui hash Perl?

Dacă am un hash de tip Perl cu o grămadă de perechi (cheie, valoare), care este metoda preferată de iterare prin toate cheile? Am auzit că folosirea fiecare poate avea într-un fel efecte secundare nedorite. Deci, este adevărat și este una dintre cele două metode de mai jos, sau există o cale mai bună?

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
0
adăugat editat

8 răspunsuri

Regula principală este de a utiliza funcția cea mai potrivită nevoilor dvs.

Dacă doriți doar cheile și nu intenționați să citească oricare dintre valori, utilizați tastele ():

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

Dacă doriți doar valorile, folosiți valorile ():

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

Dacă aveți nevoie de tastele și valorile, utilizați fiecare ():

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

Dacă intenționați să schimbați cheile hash-ului în orice mod cu excepția pentru a șterge cheia curentă în timpul iterației, atunci nu trebuie să utilizați fiecare (). De exemplu, acest cod pentru a crea un set nou de taste majuscule cu valori dublate funcționează bine cu ajutorul tastelor ():

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

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

producerea hash-ului rezultat:

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

Dar folosind fiecare () pentru a face același lucru:

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

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

produce rezultate incorecte în moduri greu de anticipat. De exemplu:

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

Cu toate acestea, este sigur:

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

Toate acestea sunt descrise în documentația perl:

% perldoc -f keys
% perldoc -f each
0
adăugat
Există o altă avertisment cu fiecare. Iteratorul este legat de hash, nu de context, ceea ce înseamnă că nu este reintrodus. De exemplu, dacă faceți buclă peste hash și tipăriți hash, perl va reseta intern iteratorul, făcând acest buclă fără sfârșit: my% hash = (a => 1, b => 2, c => 3); în timp ce (($ k, $ v) = fiecare% hash) {print% hash; } Citiți mai multe la blogs.perl.org/ utilizatorii / rurban / 2014/04 / do-nu-uz-each.html
adăugat autor Rawler
Adăugați un chei de contexte void% h; înainte de fiecare buclă să se arate în siguranță folosind iteratorul.
adăugat autor ysth

Câteva gânduri diverse despre acest subiect:

  1. Nu există nimic sigur pentru nici unul dintre iteratorii hash-ului. Ceea ce nu este sigur este modificarea cheilor unui hash în timp ce iterați peste el. (Este perfect sigur să modificăm valorile.) Singurul efect secundar pe care-l pot ghida este că valorile returnează aliasuri, ceea ce înseamnă că modificarea lor va modifica conținutul hash-ului. Acest lucru este de design, dar nu poate fi ceea ce doriți în anumite circumstanțe.
  2. răspunsul acceptat de John este bun cu o singură excepție: documentația este clară că nu este sigur să adăugați chei în timp ce iterați peste un hash. Poate funcționa pentru anumite seturi de date, dar va eșua pentru alții în funcție de ordinea hash.
  3. După cum sa menționat deja, este sigur să ștergeți ultima cheie returnată de each . Acest lucru este nu adevărat pentru tastele ca fiecare este un iterator în timp ce
0
adăugat
Re "nu este adevărat pentru chei", mai degrabă: nu se aplică cheilor și orice ștergere este sigură. Expresia pe care o folosiți presupune că nu este niciodată sigur să ștergeți nimic atunci când utilizați cheile.
adăugat autor ysth
Re: "nimic nesigur despre oricare dintre iteratorii hash", celălalt pericol este presupunerea că iteratorul este la început înainte de a începe fiecare buclă, după cum o menționează și alții.
adăugat autor ysth

De obicei folosesc cheile și nu mă gândesc la ultima dată când am folosit sau citesc o utilizare a fiecare .

Nu uitați de map , în funcție de ceea ce faceți în buclă!

map { print "$_ => $hash{$_}\n" } keys %hash;
0
adăugat
nu folosiți harta dacă nu doriți valoarea returnată
adăugat autor ko-dos

Un lucru pe care ar trebui să îl cunoașteți atunci când utilizați fiecare este că are efectul secundar al adăugării "stării" la hash (hash-ul trebuie să-și amintească ce este "următoarea" cheie). Când utilizați codul ca fragmentele publicate mai sus, care repeta întregul hash într-un singur pas, de obicei nu este a problemă. Cu toate acestea, veți întâlni probleme greu de identificat (vorbesc de la experiență;), atunci când se utilizează fiecare împreună cu declarații ca last sau return pentru a ieși din în timp ce ... fiecare înainte de tine au procesat toate cheile.

În acest caz, hash-ul își va aminti ce chei a returnat deja, și atunci când utilizați fiecare pe el data viitoare (poate într-o bucată totală neînrudită) cod), va continua la această poziție.

Exemplu:

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

Aceasta imprimă:

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

Ce sa întâmplat cu cheile "bar" și baz "? Sunt încă acolo, dar al doilea fiecare începe atunci când primul se oprește și se oprește când ajunge la sfârșitul hash-ului, astfel că nu le vedem niciodată în a doua bucla.

0
adăugat

Eu folosesc întotdeauna metoda 2, de asemenea. Singurul beneficiu al utilizării fiecăruia este dacă citiți (mai degrabă decât re-alocați) valoarea intrării hash, nu faceți în mod constant referire la hash.

0
adăugat

S-ar putea sa ma muscat de asta, dar cred ca este preferinta personala. Nu găsesc nicio referință în doc-uri pentru fiecare () care să fie diferită de chei () sau valori () (altele decât răspunsul evident "ei întorc diferite lucruri" răspuns.) De fapt, docs afirmă utilizarea aceluiași iterator și toți returnați valorile listei actuale în loc de copii ale acestora și că modificarea hash-ului în timp ce iterați peste el folosind orice apel este rău.

Tot ce am spus, aproape că folosesc mereu tastele () pentru că pentru mine este de obicei mai auto-documentat pentru a accesa valoarea cheii prin hash-ul în sine. Folosesc ocazional valori () când valoarea este o referință la o structură mare și cheia pentru hash a fost deja stocată în structură, moment în care cheia este redundantă și nu am nevoie de ea. Cred că am folosit fiecare () de 2 ori în 10 ani de programare Perl și a fost probabil alegerea greșită de ambele ori =)

0
adăugat

Locul unde fiecare vă poate provoca probleme este că este un iterator adevărat, fără scop. De exemplu:

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

Dacă trebuie să fiți sigur că fiecare obține toate cheile și valorile, trebuie să vă asigurați mai întâi că utilizați chei sau values ​​ reseta iteratorul). Consultați documentația pentru fiecare .

0
adăugat
Acest lucru poate fi mușcat în a $$ dacă nu este atent
adăugat autor sdkks

Voi spune:

  1. Utilizați ceea ce este mai ușor de citit / înțeles pentru majoritatea oamenilor (de exemplu, chei, de obicei, aș fi argumentat)
  2. Folosiți tot ceea ce decideți în mod consecvent prin întreaga bază de cod.

Acest lucru oferă două avantaje majore:

  1. Este mai ușor să vedeți codul "comun", astfel încât să puteți re-factoriza în funcții / metode.
  2. Este mai ușor pentru dezvoltatorii viitori să se mențină.

Nu cred că este mai scump să folosiți cheile deasupra fiecăruia, deci nu este nevoie de două construcții diferite pentru același lucru în codul dvs.

0
adăugat
Cu keys , utilizarea memoriei crește cu hash-size * avg-key-size . Având în vedere că dimensiunea cheii este limitată doar de memorie (fiindcă ele sunt doar elemente de matrice precum "valorile lor corespunzătoare" sub capotă), în unele situații poate fi prohibitiv mai scump atât în ​​utilizarea memoriei, cât și în timpul luate pentru a face copia.
adăugat autor Adrian Günter