Quel est le moyen le plus sûr de parcourir les clés d'un hachage Perl?

Si j'ai un hachage Perl avec un tas de paires (clé, valeur), quelle est la méthode préférée d'itération à travers toutes les clés? J'ai entendu dire qu'utiliser each peut avoir des effets secondaires inattendus. Donc, est-ce vrai, et est l'une des deux méthodes suivantes, ou existe-t-il un meilleur moyen?

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

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

8 Réponses

La règle générale est d'utiliser la fonction la plus adaptée à vos besoins.

Si vous voulez simplement les clés et ne prévoyez pas de lire l'une des valeurs, utilisez keys ():

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

Si vous voulez juste les valeurs, utilisez values ​​():

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

Si vous avez besoin des valeurs et , utilisez each ():

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

Si vous envisagez de modifier les clés du hachage de quelque manière que ce soit sauf pour supprimer la clé en cours pendant l'itération, vous ne devez pas utiliser each (). Par exemple, ce code pour créer un nouveau jeu de clés majuscules avec des valeurs doublées fonctionne correctement avec keys ():

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

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

produire le hachage résultant attendu:

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

Mais en utilisant chacun() pour faire la même chose:

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

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

produit des résultats incorrects de manière difficile à prévoir. Par exemple:

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

Ceci, cependant, est sûr:

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

Tout ceci est décrit dans la documentation perl:

% perldoc -f keys
% perldoc -f each
0
ajouté
Il y a une autre mise en garde avec chacun. L'itérateur est lié au hachage, pas au contexte, ce qui signifie qu'il n'est pas réentrant. Par exemple, si vous bouclez un hachage et que vous imprimez le hachage, perl réinitialisera l'itérateur en interne, ce qui rendra ce code infini: my% hash = (a => 1, b => 2, c => 3,); while (my ($ k, $ v) = chaque hash%) {print% hash; } Lire la suite sur blogs.perl.org/ users / rurban / 2014/04 / do-not-use-each.html
ajouté l'auteur Rawler, source
Veuillez ajouter une clé de contexte vide% h; avant chaque chaque boucle pour montrer en toute sécurité en utilisant l'itérateur.
ajouté l'auteur ysth, source

Quelques réflexions sur ce sujet:

  1. Il n'y a rien de dangereux sur les itérateurs eux-mêmes. Ce qui est dangereux, c'est de modifier les clés d'un hachage pendant que vous l'itérez. (Il est parfaitement sûr de modifier les valeurs.) Le seul effet secondaire potentiel que je peux penser est que values ​​ renvoie des alias ce qui signifie que les modifier va modifier le contenu du hachage. C'est par conception mais peut ne pas être ce que vous voulez dans certaines circonstances.
  2. John's réponse acceptée est bon à une exception près: la documentation indique clairement qu'il n'est pas sûr d'ajouter des clés pendant l'itération sur un hachage. Cela peut fonctionner pour certains ensembles de données mais échouer pour d'autres en fonction de l'ordre de hachage.
  3. Comme indiqué précédemment, il est prudent de supprimer la dernière clé renvoyée par chaque . Ce n'est pas vrai pour les clés car chaque est un itérateur alors que keys renvoie une liste.
0
ajouté
Re "pas vrai pour les clés", plutôt: il ne s'applique pas aux touches et toute suppression est en sécurité. Le phrasé que vous utilisez implique qu'il n'est jamais prudent de supprimer quoi que ce soit lorsque vous utilisez des clés.
ajouté l'auteur ysth, source
Re: "rien de dangereux sur l'un des itérateurs de hachage", l'autre danger est de supposer que l'itérateur est au début avant de commencer chaque boucle, comme d'autres le mentionnent.
ajouté l'auteur ysth, source

J'utilise généralement keys et je ne peux pas penser à la dernière fois que j'ai utilisé ou lu une utilisation de chaque .

Ne pas oublier map , en fonction de ce que vous faites dans la boucle!

map { print "$_ => $hash{$_}\n" } keys %hash;
0
ajouté
N'utilisez pas la carte sauf si vous voulez la valeur de retour
ajouté l'auteur ko-dos, source

Une chose que vous devez savoir lorsque vous utilisez chaque est que l'effet secondaire d'ajouter "état" à votre hachage (le hachage doit se rappeler quelle est la "prochaine" clé). Lorsque vous utilisez du code comme les extraits affichés ci-dessus, qui parcourent tout le hachage en une fois, ce n'est généralement pas problème. Cependant, vous rencontrerez des problèmes difficiles à résoudre (je parle de expérience;), en utilisant each avec des déclarations comme last ou return pour quitter la boucle while ... each avant ont traité toutes les clés.

Dans ce cas, le hachage se souviendra des clés qu'il a déjà renvoyées, et quand vous utilisez each dessus la prochaine fois (peut-être dans un morceau de code), il continuera à cette position.

Exemple:

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

Cela imprime:

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

Qu'est-il arrivé aux touches "bar" et baz "? Ils sont toujours là, mais le le second each commence là où le premier s'est arrêté, et s'arrête quand il atteint la fin du hachage, donc nous ne les voyons jamais dans la deuxième boucle.

0
ajouté

J'utilise toujours la méthode 2 aussi. Le seul avantage de l'utilisation de chacun est que si vous lisez simplement (plutôt que de réattribuer) la valeur de l'entrée de hachage, vous n'êtes pas constamment en train de dé-référencer le hachage.

0
ajouté

Je peux me faire mordre par celui-ci mais je pense que c'est une préférence personnelle. Je ne trouve pas de référence dans les docs à each() étant différente de keys() ou values ​​() (autre que la réponse évidente "they return different things".) En fait, les docs indiquent l'utilisation du même itérateur et tous renvoyer les valeurs de la liste réelle au lieu des copies de celles-ci, et que modifier le hachage en l'itérant sur n'importe quel appel est mauvais.

Cela dit, j'utilise presque toujours des touches() car pour moi, c'est généralement plus auto-documenté pour accéder à la valeur de la clé via le hachage lui-même. J'utilise occasionnellement values ​​() quand la valeur est une référence à une grande structure et que la clé du hash était déjà stockée dans la structure, à quel point la clé est redondante et je n'en ai pas besoin. Je pense que j'ai utilisé chaque() 2 fois dans 10 ans de programmation Perl et c'était probablement le mauvais choix les deux fois =)

0
ajouté

L'endroit où each peut vous causer des problèmes est qu'il s'agit d'un véritable itérateur non défini. A titre d'exemple:

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

Si vous devez vous assurer que each obtient toutes les clés et valeurs, vous devez vous assurer d'utiliser les clés ou valeurs d'abord (comme réinitialise l'itérateur). Voir la documentation pour chaque .

0
ajouté
Cela peut mordre dans le $$ si pas prudent
ajouté l'auteur sdkks, source

Je dirais:

  1. Utilisez ce qui est le plus facile à lire / comprendre pour la plupart des gens (donc les clés, d'habitude, je dirais)
  2. Utilisez ce que vous décidez de manière cohérente dans toute la base du code.

Cela donne 2 avantages majeurs:

  1. Il est plus facile de repérer le code "commun" afin de pouvoir le factoriser en fonctions / methiods.
  2. Il est plus facile pour les futurs développeurs de le maintenir.

Je ne pense pas qu'il soit plus coûteux d'utiliser des clés sur chacun, donc pas besoin de deux constructions différentes pour la même chose dans votre code.

0
ajouté
Avec keys , l'utilisation de la mémoire augmente de hash-size * avg-key-size . Étant donné que la taille de la clé n'est limitée que par la mémoire (car ils ne sont que des éléments de tableau comme "leurs" valeurs sous le capot), dans certaines situations, il peut être prohibitivement plus cher en mémoire et en temps pris pour faire la copie.
ajouté l'auteur Adrian Günter, source