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

107

Si j'ai un hachage Perl avec un tas de paires (clé, valeur), quelle est la méthode préférée pour parcourir toutes les clés? J'ai entendu dire que l'utilisation eachpeut d'une certaine manière avoir des effets secondaires involontaires. Alors, est-ce vrai, et est-ce que l'une des deux méthodes suivantes est la meilleure ou y a-t-il une meilleure façon?

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

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
Rudd Zwolinski
la source

Réponses:

199

La règle de base est d'utiliser la fonction la plus adaptée à vos besoins.

Si vous voulez juste les clés et ne prévoyez jamais de lire aucune 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 clés et des valeurs, 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é actuelle pendant l'itération, vous ne devez pas utiliser each (). Par exemple, ce code pour créer un nouvel ensemble de clés majuscules avec des valeurs doublées fonctionne très bien en utilisant 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 each () 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 cela est décrit dans la documentation perl:

% perldoc -f keys
% perldoc -f each
John Siracusa
la source
6
Veuillez ajouter une clé de contexte vide% h; avant chaque boucle pour afficher en toute sécurité en utilisant l'itérateur.
ysth
5
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 rentrant. Par exemple, si vous bouclez sur un hachage, et imprimez le hachage, perl réinitialisera en interne l'itérateur, ce qui rendra ce code en boucle sans fin: my% hash = (a => 1, b => 2, c => 3,); while (mon ($ k, $ v) = chaque% hachage) {print% hachage; } En savoir plus sur blogs.perl.org/users/rurban/2014/04/do-not-use-each.html
Rawler
28

Une chose dont vous devez être conscient lors de l'utilisation eachest que cela a pour effet secondaire d'ajouter un "état" à votre hachage (le hachage doit se souvenir de ce qu'est la clé "suivante"). Lorsque vous utilisez du code comme les extraits publiés ci-dessus, qui itèrent sur tout le hachage en une seule fois, ce n'est généralement pas un problème. Cependant, vous rencontrerez des problèmes difficiles à localiser (je parle d'expérience;), lors de l'utilisation eachavec des instructions comme lastou returnpour sortir de la while ... eachboucle avant d'avoir traité toutes les clés.

Dans ce cas, le hachage se souviendra des clés qu'il a déjà renvoyées, et lorsque vous l'utiliserez eachla prochaine fois (peut-être dans un morceau de code totalement indépendant), 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 "? Elles sont toujours là, mais la seconde eachcommence là où la première s'est arrêtée et s'arrête quand elle atteint la fin du hachage, donc on ne les voit jamais dans la seconde boucle.

8jean
la source
22

L'endroit où cela eachpeut vous causer des problèmes est qu'il s'agit d'un véritable itérateur sans portée. 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 avez besoin d'être sûr que eachtoutes les clés et valeurs sont obtenues, vous devez vous assurer que vous utilisez keysou d' valuesabord (car cela réinitialise l'itérateur). Consultez la documentation de chacun .

Darren Meyer
la source
14

L'utilisation de chaque syntaxe empêchera la génération de l'ensemble de clés en même temps. Cela peut être important si vous utilisez un hachage lié à une base de données avec des millions de lignes. Vous ne voulez pas générer la liste complète des clés en une seule fois et épuiser votre mémoire physique. Dans ce cas, chacun sert d'itérateur alors que les clés génèrent en fait le tableau entier avant le début de la boucle.

Ainsi, le seul endroit où «chacun» est réellement utile est lorsque le hachage est très important (par rapport à la mémoire disponible). Cela ne se produira probablement que lorsque le hachage lui-même ne vit pas dans la mémoire elle-même, à moins que vous ne programmiez un appareil de collecte de données portable ou quelque chose avec une petite mémoire.

Si la mémoire n'est pas un problème, le paradigme de la carte ou des clés est généralement le paradigme le plus important et le plus facile à lire.


la source
6

Quelques réflexions diverses sur ce sujet:

  1. Il n'y a rien de dangereux à propos des itérateurs de hachage eux-mêmes. Ce qui n'est pas sûr, 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 valuesauquel je puisse penser est que les alias retournent, ce qui signifie que leur modification modifiera le contenu du hachage. C'est par conception, mais peut ne pas être ce que vous voulez dans certaines circonstances.
  2. La réponse acceptée par John est bonne à une exception près: la documentation indique clairement qu'il n'est pas sûr d'ajouter des clés lors d'une itération sur un hachage. Cela peut fonctionner pour certains ensembles de données mais échouera pour d'autres en fonction de l'ordre de hachage.
  3. Comme indiqué précédemment, vous pouvez supprimer en toute sécurité la dernière clé renvoyée par each. Ceci est pas vrai pour keysque eachest un itérateur tout keysretourne une liste.
Michael Carman
la source
2
Re "pas vrai pour les clés", plutôt: cela ne s'applique pas aux clés et toute suppression est sûre. Le libellé que vous utilisez implique qu'il n'est jamais sûr de supprimer quoi que ce soit lors de l'utilisation de clés.
ysth
2
Re: "rien de dangereux pour aucun des itérateurs de hachage", l'autre danger est de supposer que l'itérateur est au début avant de démarrer une boucle, comme d'autres le mentionnent.
ysth
3

J'utilise toujours aussi la méthode 2. 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 ne dé-référencerez pas constamment le hachage.

Jaredg
la source
3

Je peux me faire mordre par celui-ci mais je pense que c'est une préférence personnelle. Je ne trouve aucune référence dans la documentation indiquant que chaque () est différent de keys () ou values ​​() (autre que la réponse évidente "ils renvoient des choses différentes". En fait, la documentation déclare utiliser le même itérateur et ils renvoie des valeurs de liste réelles au lieu de copies de celles-ci, et que modifier le hachage en l'itérant à l'aide d'un appel est mauvais.

Cela dit, j'utilise presque toujours keys () car pour moi, il est généralement plus auto-documenté d'accéder à la valeur de la clé via le hachage lui-même. J'utilise parfois values ​​() lorsque la valeur est une référence à une grande structure et que la clé du hachage é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é each () 2 fois en 10 ans de programmation Perl et c'était probablement le mauvais choix les deux fois =)

jj33
la source
2

J'utilise habituellement keyset je ne peux pas penser à la dernière fois que j'ai utilisé ou lu une utilisation de each.

N'oubliez pas map, en fonction de ce que vous faites dans la boucle!

map { print "$_ => $hash{$_}\n" } keys %hash;
Gary Richardson
la source
6
n'utilisez pas la carte à moins que vous ne vouliez la valeur de retour
ko-dos
-1

Je dirais:

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

Cela donne 2 avantages majeurs:

  1. Il est plus facile de repérer le code «commun» afin que vous puissiez re-factoriser en fonctions / méthodes.
  2. C'est plus facile à maintenir pour les futurs développeurs.

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

Hogsmill
la source
1
Avec l' keysutilisation 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 ce ne sont que des éléments de tableau comme «leurs» valeurs correspondantes sous le capot), dans certaines situations, cela peut être prohibitif en termes d'utilisation de la mémoire et de temps nécessaire pour faire la copie.
Adrian Günter