Meilleur moyen d'itérer dans un tableau Perl


94

Quelle est la meilleure implémentation (en termes de vitesse et d'utilisation de la mémoire) pour itérer dans un tableau Perl? Y a-t-il une meilleure façon? ( @Arrayne doit pas être conservé).

Mise en œuvre 1

foreach (@Array)
{
      SubRoutine($_);
}

Mise en œuvre 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

Mise en œuvre 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

Mise en œuvre 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

Mise en œuvre 5

map { SubRoutine($_) } @Array ;

2
Pourquoi y aurait-il un «meilleur»? Surtout étant donné que nous n'avons aucune idée de la façon dont vous vous mesureriez l'un contre l'autre (la vitesse est-elle plus importante que l'utilisation de la mémoire? Est mapune réponse acceptable ?. etc.)
Max Lybbert

2
Deux des trois que vous avez postés me feraient dire "WTH?!" à moins qu'il n'y ait un contexte environnant supplémentaire pour en faire des alternatives raisonnables. Dans tous les cas, cette question se situe au niveau de " Quelle est la meilleure façon d'ajouter deux nombres? " La plupart du temps, il n'y a qu'une seule façon. Ensuite, il y a ces circonstances, où vous avez besoin d'une manière différente. Vote de clôture.
Sinan Ünür

4
@ SinanÜnür Je compatis avec votre opinion (qu'il n'y a qu'une seule façon d'ajouter deux chiffres), mais l'analogie n'est pas assez forte pour être dédaignée. De toute évidence, il y a plus d'un moyen, et le PO veut comprendre ce qui est une bonne idée et ce qui ne l'est pas.
CodeClown42

2
Le chapitre 24 de la troisième édition de Programming Perl contient une section sur l'efficacité qui est une bonne lecture. Il aborde les différents types d'efficacité tels que le temps, le programmeur, le mainteneur. La section commence par la déclaration "Notez que l'optimisation pour le temps peut parfois vous coûter en espace ou en efficacité du programmeur (indiqué par des indices contradictoires ci-dessous). Ce sont les pauses."

1
Une 1 façon d'ajouter deux nombres? Pas si vous regardez dans les appels de niveau inférieur / implémentations .... pensez carry préanalyse, etc. additionneurs à sauvegarde
WorkWise

Réponses:


76
  • En termes de vitesse: # 1 et # 4, mais pas de beaucoup dans la plupart des cas.

    Vous pourriez écrire un benchmark pour confirmer, mais je suppose que vous trouverez que les n ° 1 et 4 sont légèrement plus rapides car le travail d'itération est effectué en C au lieu de Perl, et aucune copie inutile des éléments du tableau ne se produit. ( $_est aliasé sur l'élément dans # 1, mais # 2 et # 3 copient en fait les scalaires du tableau.)

    # 5 pourrait être similaire.

  • En termes d'utilisation de la mémoire: ils sont tous les mêmes sauf pour # 5.

    for (@a)est un boîtier spécial pour éviter d'aplatir le tableau. La boucle itère sur les index du tableau.

  • En termes de lisibilité: # 1.

  • En termes de flexibilité: # 1 / # 4 et # 5.

    # 2 ne prend pas en charge les éléments qui sont faux. Les n ° 2 et 3 sont destructeurs.


3
Wow, vous avez ajouté des tonnes d'informations dans des phrases courtes et simples.
jaypal singh

1
# 2 est bon lorsque vous faites des files d'attente (par exemple, des recherches en largeur d'abord):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

L'implémentation 4 ne crée-t-elle pas un tableau intermédiaire d'indices, ce qui pourrait introduire une grande quantité de mémoire à utiliser? Si tel est le cas, il semble que l'on ne devrait pas utiliser cette approche. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning

@ikegami Fidèle à votre style de champion - excellente réponse :)
skeetastax

26

Si vous ne vous souciez que des éléments de @Array, utilisez:

for my $el (@Array) {
# ...
}

ou

Si les indices comptent, utilisez:

for my $i (0 .. $#Array) {
# ...
}

Ou, à partir de la version perl5.12.1, vous pouvez utiliser:

while (my ($i, $el) = each @Array) {
# ...
}

Si vous avez besoin à la fois de l'élément et de son index dans le corps de la boucle, Je m'attendrais en utilisant each être le plus rapide, mais alorsvous renoncerez à la compatibilité avec les versions antérieures à 5.12.1 perl.

Un autre modèle que ceux-ci pourrait être approprié dans certaines circonstances.


Je m'attendrais eachà ce que ce soit le plus lent. Il fait tout le travail des autres moins un alias, plus une affectation de liste, deux copies scalaires et deux effacements scalaires.
ikegami

Et, au mieux de ma capacité de mesure, vous avez raison. Environ 45% plus rapide avec l' foritération sur les indices d'un tableau, et 20% plus rapide lors de l'itération sur les index d'une référence de tableau (j'accède $array->[$i]dans le corps), sur l'utilisation eachen conjonction avec while.
Sinan Ünür

3

IMO, la mise en œuvre n ° 1 est typique et étant courte et idiomatique pour Perl l'emporte sur les autres pour cela seul. Un benchmark des trois choix pourrait vous offrir au moins un aperçu de la vitesse.


2

1 est sensiblement différent de 2 et 3, car il laisse le réseau intact, tandis que les deux autres le laissent vide.

Je dirais que le n ° 3 est assez farfelu et probablement moins efficace, alors oubliez cela.

Ce qui vous laisse avec le n ° 1 et le n ° 2, et ils ne font pas la même chose, donc l'un ne peut pas être "meilleur" que l'autre. Si le tableau est volumineux et que vous n'avez pas besoin de le conserver, la portée le traitera généralement ( mais voir NOTE ), donc généralement , # 1 est toujours la méthode la plus claire et la plus simple. Décaler chaque élément n'accélérera rien. Même s'il est nécessaire de libérer le tableau de la référence, j'irais simplement:

undef @Array;

lorsque vous avez terminé.

  • REMARQUE : le sous-programme contenant la portée du tableau conserve en fait le tableau et réutilise l'espace la prochaine fois. En général , cela devrait être bien (voir les commentaires).

@Array = ();ne libère pas le tableau sous-jacent. Ne même pas sortir du champ de vision ferait cela. Si vous vouliez libérer le tableau sous-jacent, vous auriez utilisé undef @Array;.
ikegami

2
Démo; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
ikegami

QUELLE??? J'avais pensé que tout l'intérêt de GC était autrefois un nombre de ref == 0, la mémoire impliquée devient recyclable.
CodeClown42

@ikegami: Je vois le problème avec ()vs undef, mais si sortir de la portée ne libère pas la mémoire utilisée par un tableau local dans cette portée, cela ne fait-il pas de Perl un désastre de fuite? Cela ne peut pas être vrai.
CodeClown42

Ils ne fuient pas non plus. Le sous-marin les possède toujours et les réutilisera la prochaine fois que le sous-marin sera appelé. Optimisé pour la vitesse.
ikegami

1

En une seule ligne pour imprimer l'élément ou le tableau.

print $ _ pour (@array);

NOTE: rappelez-vous que $ _ fait référence en interne à l'élément de @array en boucle. Toutes les modifications apportées à $ _ seront reflétées dans @array; ex.

my @array = qw( 1 2 3 );
for (@array) {
        $_ = $_ *2 ;
}
print "@array";

sortie: 2 4 6


0

La meilleure façon de décider de questions comme celle-ci pour les comparer:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    foreach my $element (@array) {
       die unless $index == $element;
       $index++;
    }
};

my $b = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (defined(my $element = shift @array)) {
       die unless $index == $element;
       $index++;
    }
};

my $c = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (scalar(@array) !=0) {
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    }
};

my $d = sub {
    my @array = @{[ @input_array ]};
    foreach my $index (0.. $#array) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $e = sub {
    my @array = @{[ @input_array ]};
    for (my $index = 0; $index <= $#array; $index++) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $f = sub {
    my @array = @{[ @input_array ]};
    while (my ($index, $element) = each @array) {
       die unless $index == $element;
    }
};

my $count;
timethese($count, {
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
});

Et l'exécuter sur perl 5, version 24, subversion 1 (v5.24.1) construit pour x86_64-linux-gnu-thread-multi

Je reçois:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

Ainsi, le 'foreach (@Array)' est environ deux fois plus rapide que les autres. Tous les autres sont très similaires.

@ikegami souligne également qu'il existe de nombreuses différences dans ces implémentations autres que la vitesse.


1
La comparaison $index < $#arraydevrait en fait être $index <= $#arraycar ce $#arrayn'est pas la longueur du tableau mais le dernier index de celui-ci.
josch
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.