Domanda

Se ho un hash Perl con un gruppo di coppie (chiave, valore), qual è il metodo preferito per scorrere tutte le chiavi?Ne ho sentito parlare each potrebbe in qualche modo avere effetti collaterali indesiderati.Quindi, è vero ed è uno dei due metodi seguenti il ​​migliore o esiste un modo migliore?

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
È stato utile?

Soluzione

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

Se vuoi solo le chiavi e non hai intenzione di farlo mai Leggere uno qualsiasi dei valori, utilizzare keys():

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

Se vuoi solo i valori, usa valori():

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

Se ti servono le chiavi E i valori, utilizzare ciascuno():

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

Se prevedi di modificare in qualsiasi modo le chiavi dell'hash tranne per eliminare la chiave corrente durante l'iterazione, non è necessario utilizzare ciascuno().Ad esempio, questo codice per creare un nuovo set di chiavi maiuscole con valori raddoppiati funziona bene utilizzando 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 ciascuno() 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

Altri suggerimenti

Una cosa di cui dovresti essere consapevole quando lo usi each è che ha l'effetto collaterale di aggiungere "stato" al tuo hash (l'hash deve ricordare quale sia la chiave "successiva").Quando si utilizzano codice come gli snippet pubblicati sopra, che iterano sull'intero hash in una volta, questo di solito non è un problema.Tuttavia, ti imbatterai in difficoltà a rintracciare i problemi (parlo per esperienza;) quando uso each insieme ad affermazioni comelast O return per uscire dal while ... each Loop prima di aver elaborato tutte le chiavi.

In questo caso, l'hash ricorderà quali chiavi è già tornato e quando si usa each La prossima volta (forse in un totale di codice 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

Cosa è successo ai tasti "bar" e baz"?Sono ancora lì, ma il secondo each inizia da dove si era interrotto il primo e si ferma quando raggiunge la fine dell'hash, quindi non li vediamo mai nel secondo ciclo.

Il posto dove each può causare problemi è che è un vero 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 esserne sicuro each ottiene tutte le chiavi e i valori che devi assicurarti di utilizzare keys O values prima (poiché ciò reimposta l'iteratore).Vedi il documentazione per ciascuno.

L'utilizzo di ciascuna sintassi impedirà la generazione simultanea dell'intero set di chiavi.Questo può essere importante se stai utilizzando 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 ciascuno funge da iteratore mentre le chiavi generano effettivamente l'intero array prima dell'inizio del ciclo.

Quindi, l'unico posto in cui "ciascuno" è 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 portatile di raccolta dati 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.

Alcune riflessioni varie su questo argomento:

  1. Non c'è nulla di pericoloso negli stessi iteratori di hash.Ciò che non è sicuro è modificare le chiavi di un hash mentre lo stai ripetendo.(È perfettamente sicuro modificare i valori.) L'unico potenziale effetto collaterale a cui riesco a pensare è questo values restituisce degli alias, il che significa che modificarli modificherà il contenuto dell'hash.Questo è previsto dalla progettazione, ma potrebbe non essere quello desiderato in alcune circostanze.
  2. Giovanni risposta accettata va bene con una 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 dell'hash.
  3. Come già notato, è sicuro eliminare l'ultima chiave restituita da each.Questo è non vero per keys COME each è un iteratore while keys restituisce un elenco.

Io uso sempre anche il metodo 2.L'unico vantaggio dell'utilizzo di ciascuno è che se stai semplicemente leggendo (invece di riassegnare) il valore della voce hash, non stai costantemente dereferenziando l'hash.

Potrei essere morso da questo, ma penso che sia una preferenza personale.Non riesco a trovare alcun riferimento nei documenti al fatto che ciascuno() sia diverso da chiavi() o valori() (a parte l'ovvia risposta "restituiscono cose diverse".In effetti i documenti affermano di utilizzare lo stesso iteratore e tutti restituiscono valori di elenco effettivi invece di copie degli stessi, e che modificare l'hash durante l'iterazione su di esso utilizzando qualsiasi chiamata è negativo.

Detto questo, utilizzo quasi sempre keys() perché per me di solito è più autodocumentato accedere al valore della chiave tramite l'hash stesso.Di tanto in tanto uso valori() 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 ciascuno() 2 volte in 10 anni di programmazione Perl e probabilmente è stata la scelta sbagliata entrambe le volte =)

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

Non dimenticartene map, a seconda di cosa stai facendo nel loop!

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

io direi:

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

Questo dà 2 grandi vantaggi:

  1. È più semplice individuare il codice "comune" in modo da poterlo rifattorizzare in funzioni/metodi.
  2. È più facile da mantenere per i futuri sviluppatori.

Non penso che sia più costoso utilizzare le chiavi su ciascuna, quindi non sono necessari due costrutti diversi per la stessa cosa nel codice.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top