web-dev-qa-db-ja.com

Perl配列を反復処理する最良の方法

Perl配列を反復処理するための(速度とメモリ使用量の点で)最適な実装はどれですか?より良い方法はありますか? (@Arrayを保持する必要はありません)。

実装1

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

実装2

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

実装3

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

実装4

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

実装5

map { SubRoutine($_) } @Array ;
91
Jean
  • 速度の点では、#1と#4ですが、ほとんどの場合はそれほどではありません。

    確認のためにベンチマークを書くことはできますが、反復作業はPerlではなくCで行われ、配列要素の不必要なコピーは発生しないため、#1と#4が少し速くなると思います。 ($_は、#1の要素に対してエイリアスですが、実際には#2および#3 コピー配列からのスカラーです。)

    #5も同様です。

  • メモリ使用量に関して:#5を除き、すべて同じです。

    for (@a)は、配列の平坦化を避けるために特別なケースです。ループは、配列のインデックスを反復処理します。

  • 読みやすさの点で:#1。

  • 柔軟性の観点から:#1 /#4および#5。

    #2はfalseの要素をサポートしません。 #2と#3は破壊的です。

73
ikegami

@Arrayの要素のみに関心がある場合は、次を使用します。

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

または

インデックスが重要な場合は、次を使用します。

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

または、Perl 5.12.1以降、次を使用できます。

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

ループの本文に要素とそのインデックスの両方が必要な場合、 期待する eachを使用 最速になるが、その後 5.12.1より前のPerlsとの互換性を放棄することになります。

特定の状況では、これら以外のパターンが適切な場合があります。

23
Sinan Ünür

IMO、実装#1は典型的であり、Perlにとって短くて慣用的であるため、それだけで他のものに勝ります。 3つの選択肢のベンチマークは、少なくとも速度に関する洞察を提供するかもしれません。

3
JRFerguson

1は配列をそのままにして、他の2つは空のままにするため、2と3とは大きく異なります。

#3はかなり奇抜で、おそらく効率が悪いと思うので、忘れてください。

これにより、#1と#2が残り、同じことはできません。そのため、一方が他方より「優れている」ことはありません。配列が大きく、それを保持する必要がない場合、一般的にスコープはそれを処理します(しかしseeNOTE)、そのためgeneralally、#1が最も明確で、最も簡単な方法。各要素をオフにシフトしても、速度は上がりません。参照から配列を解放する必要がある場合でも、私はただ行きます:

undef @Array;

それが終わったら。

  • NOTE:配列のスコープを含むサブルーチンは、実際に配列を保持し、次回にスペースを再利用します。 一般的に、それは問題ないはずです(コメントを参照)。

要素または配列を印刷する単一行。

(@array)に$ _を出力します。

注:$ _はループ内の@arrayの要素を内部的に参照していることに注意してください。$ _で行った変更は@arrayに反映されます; ex。

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

出力:2 4 6

0
Sandeep_black

これらの質問をベンチマークするためにこのような質問を決定する最良の方法:

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,
});

そしてこれをx86_64-linux-gnu-thread-multi用にビルドされたPerl 5バージョン24、Subversion 1(v5.24.1)で実行します

私は得る:

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)

したがって、「foreach(@Array)」は他の2倍の速度です。他のすべては非常に似ています。

@ikegamiはまた、これらのインプリメンテーションには速度以外にもかなりの違いがあることを指摘しています。

0