Qual è il modo più sicuro per scorrere le chiavi di un hash Perl?


107

Se ho un hash Perl con un mucchio di coppie (chiave, valore), qual è il metodo preferito per scorrere tutte le chiavi? Ho sentito che l'uso eachpuò in qualche modo avere effetti collaterali indesiderati. Quindi, è vero, ed è uno dei due metodi seguenti il ​​migliore, o c'è un modo migliore?

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

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

Risposte:


199

La regola pratica è utilizzare la funzione più adatta alle proprie esigenze.

Se vuoi solo le chiavi e non prevedi di leggere mai nessuno dei valori, usa keys ():

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

Se vuoi solo i valori, usa values ​​():

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

Se hai bisogno delle chiavi e dei valori, usa each ():

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

Se si prevede di modificare le chiavi dell'hash in qualsiasi modo tranne che per eliminare la chiave corrente durante l'iterazione, non è necessario utilizzare each (). Ad esempio, questo codice per creare un nuovo set di chiavi maiuscole con valori raddoppiati funziona bene usando keys ():

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

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

producendo l'hash risultante previsto:

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

Ma usando each () per fare la stessa cosa:

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

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

produce risultati errati in modi difficili da prevedere. Per esempio:

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

Questo, tuttavia, è sicuro:

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

Tutto questo è descritto nella documentazione di perl:

% perldoc -f keys
% perldoc -f each

6
Aggiungi un contesto vuoto chiavi% h; prima di ogni ciclo per mostrare in sicurezza usando l'iteratore.
ysth

5
C'è un altro avvertimento con ciascuno. L'iteratore è vincolato all'hash, non al contesto, il che significa che non è rientrante. Ad esempio, se si esegue un ciclo su un hash e si stampa l'hash, perl reimposterà internamente l'iteratore, rendendo questo codice in loop all'infinito: my% hash = (a => 1, b => 2, c => 3,); while (my ($ k, $ v) = ogni% hash) {print% hash; } Maggiori informazioni su blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler

28

Una cosa di cui dovresti essere consapevole quando lo usi eachè che ha l'effetto collaterale di aggiungere "state" al tuo hash (l'hash deve ricordare qual è la chiave "successiva"). Quando si utilizza codice come gli snippet pubblicati sopra, che iterano sull'intero hash in una volta sola, questo di solito non è un problema. Tuttavia, ti imbatterai in problemi difficili da rintracciare (parlo per esperienza;), quando usi eachinsieme a dichiarazioni come lasto returnper uscire dal while ... eachciclo prima di aver elaborato tutte le chiavi.

In questo caso, l'hash ricorderà quali chiavi è già stato restituito, e quando lo utilizzerai eachla volta successiva (magari in un pezzo di codice totalmente non correlato), continuerà in questa posizione.

Esempio:

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

Questo stampa:

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

Che fine hanno fatto i tasti "bar" e baz "? Sono ancora lì, ma il secondo eachinizia da dove si era interrotto il primo e si ferma quando raggiunge la fine dell'hash, quindi non li vediamo mai nel secondo ciclo.


22

Il punto in cui eachpuò causare problemi è che si tratta di un vero e proprio iteratore senza ambito. A titolo di esempio:

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

Se hai bisogno di essere sicuro che eachottenga tutte le chiavi e i valori, devi assicurarti di usare keyso valuesprima (poiché ciò ripristina l'iteratore). Vedere la documentazione per ciascuno .


14

L'utilizzo della sintassi each impedirà la generazione simultanea dell'intero set di chiavi. Questo può essere importante se stai usando un hash legato a un database con milioni di righe. Non vuoi generare l'intero elenco di chiavi tutto in una volta ed esaurire la tua memoria fisica. In questo caso, ognuno funge da iteratore, mentre le chiavi generano effettivamente l'intero array prima dell'inizio del ciclo.

Quindi, l'unico posto "each" è di reale utilità è quando l'hash è molto grande (rispetto alla memoria disponibile). È probabile che ciò accada solo quando l'hash stesso non vive nella memoria stessa a meno che non si stia programmando un dispositivo di raccolta dati portatile o qualcosa con poca memoria.

Se la memoria non è un problema, di solito il paradigma della mappa o delle chiavi è il paradigma più prevalente e più facile da leggere.


6

Alcuni pensieri vari su questo argomento:

  1. Non c'è nulla di pericoloso in nessuno degli stessi iteratori hash. Ciò che non è sicuro è modificare le chiavi di un hash mentre lo stai iterando. (È perfettamente sicuro modificare i valori.) L'unico potenziale effetto collaterale a cui posso pensare è che valuesrestituisce alias, il che significa che modificarli modificherà il contenuto dell'hash. Questo è di progettazione ma potrebbe non essere quello che desideri in alcune circostanze.
  2. La risposta accettata da John è buona con un'eccezione: la documentazione è chiara che non è sicuro aggiungere chiavi durante l'iterazione su un hash. Potrebbe funzionare per alcuni set di dati ma fallirà per altri a seconda dell'ordine hash.
  3. Come già notato, è sicuro eliminare l'ultima chiave restituita da each. Questo non è vero keyspoiché eachè un iteratore mentre keysrestituisce una lista.

2
Re "non vero per le chiavi", piuttosto: non è applicabile alle chiavi e qualsiasi cancellazione è sicura. La frase che usi implica che non è mai sicuro cancellare nulla quando usi le chiavi.
ysth

2
Ri: "niente di pericoloso in nessuno degli iteratori hash", l'altro pericolo è che l'iteratore sia all'inizio prima di iniziare un ciclo, come altri menzionano.
ysth

3

Uso sempre anche il metodo 2. L'unico vantaggio dell'utilizzo di ciascuno è che se stai solo leggendo (piuttosto che riassegnando) il valore della voce hash, non stai costantemente de-referenziando l'hash.


3

Potrei essere morso da questo, ma penso che sia una preferenza personale. Non riesco a trovare alcun riferimento nei documenti al fatto che ciascuna () sia diversa da chiavi () o valori () (a parte l'ovvia risposta "restituiscono cose diverse". Infatti i documenti affermano di utilizzare lo stesso iteratore e tutti restituisce i valori di lista effettivi invece di copie di essi, e che modificare l'hash mentre si itera su di esso usando qualsiasi chiamata è sbagliato.

Detto questo, uso quasi sempre keys () perché per me di solito è più auto documentante accedere al valore della chiave tramite l'hash stesso. Di tanto in tanto uso values ​​() quando il valore è un riferimento a una struttura di grandi dimensioni e la chiave dell'hash era già memorizzata nella struttura, a quel punto la chiave è ridondante e non ne ho bisogno. Penso di aver usato ciascuna () 2 volte in 10 anni di programmazione Perl ed è stata probabilmente la scelta sbagliata entrambe le volte =)


2

Di solito lo uso keyse non riesco a pensare all'ultima volta che ho usato o letto un uso di each.

Non dimenticare map, a seconda di cosa stai facendo nel ciclo!

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

6
non usare la mappa a meno che tu non voglia il valore di ritorno
ko-dos

-1

Io dirò:

  1. Usa ciò che è più facile da leggere / capire per la maggior parte delle persone (quindi le chiavi, di solito, direi)
  2. Usa tutto ciò che decidi in modo coerente attraverso l'intera base di codice.

Questo offre 2 vantaggi principali:

  1. È più facile individuare il codice "comune" in modo da poter ri-fattorizzare in funzioni / metiodi.
  2. È più facile da mantenere per i futuri sviluppatori.

Non penso che sia più costoso usare le chiavi su ciascuna, quindi non c'è bisogno di due costrutti diversi per la stessa cosa nel codice.


1
Con keysl'utilizzo della memoria aumenta di hash-size * avg-key-size. Dato che la dimensione della chiave è limitata solo dalla memoria (dato che sono solo elementi di array come i "loro" valori corrispondenti sotto il cofano), in alcune situazioni può essere proibitivamente più costosa sia nell'utilizzo della memoria che nel tempo impiegato per fare la copia.
Adrian Günter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.