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


93

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 ;

2
なぜ「ベスト」なのでしょうか?特に、どのように測定するかわからない(メモリの使用よりも速度の方が重要なのか、それともmap許容できる回答なのか、など)
Max Lybbert

2
あなたが投稿した3つのうち2つは、「WTH ?!」それらを賢明な選択肢にするための追加の周囲のコンテキストがない限り。いずれにしても、この質問は「2つの数値を加算する最良の方法は何ですか?というレベルです。ほとんどの場合、方法は1つだけです。次に、別の方法が必要な状況があります。閉じる投票。
SinanÜnür12年

4
@SinanÜnür私はあなたの意見に共感します(2つの数値を加算する方法は1つしかない)、類推は否定的に使用するには十分強力ではありません。明らかに、1つ以上の方法があり、OPは良いアイデアとそうでないものを理解したいと考えています。
CodeClown42

2
プログラミングPerlの第3版の第24章には、効率よく読むためのセクションがあります。時間、プログラマー、メンテナーなど、さまざまなタイプの効率に対応します。このセクションは、「時間を最適化すると、スペースやプログラマーの効率が低下する場合があることに注意してください(以下のヒントの矛盾により示されます)。

1
2つの数値を加算する1つの方法?下位レベルの呼び出し/実装を調べた場合は
違い

回答:


76
  • 速度に関しては、#1と#4ですが、ほとんどの場合はそうではありません。

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

    #5も同様です。

  • 用語のメモリ使用量:#5を除いてすべて同じです。

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

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

  • 柔軟性の面で:#​​1 /#4および#5。

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


3
うわー、あなたは短くて簡単な文章でトラックの情報を追加しました。
jaypal sing 2014

1
#2は、キューを実行する場合に適しています(幅優先検索など):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

実装4では、インデックスの中間配列が作成されません。これにより、大量のメモリが使用される可能性があります。もしそうなら、そのアプローチを使うべきではないように思えます。stackoverflow.com/questions/6440723/... rt.cpan.org/Public/Bug/Display.html?id=115863
トルステンSchöning

@ikegamiチャンピオンスタイルにぴったり-すばらしい答え:)
skeetastax

26

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

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

または

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

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

または、perl5.12.1以降では以下を使用できます。

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

ループの本体で要素とそのインデックスの両方が必要な場合は、 私は期待します を使用して each 最速ですが、それから5.12.1より前のバージョンとperlの互換性はあきらめます。

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


eachが最も遅いと思います。他のすべての作業からエイリアスを除いて、リストの割り当て、2つのスカラーコピー、2つのスカラークリアを行います。
池上

そして、私の測定能力の及ぶ限り、あなたは正しいです。と組み合わせて使用forするよりも、配列のインデックスを反復処理すると約45%速くなり、配列参照のインデックスを反復処理すると($array->[$i]本文でアクセスします)、20%速くなります。eachwhile
SinanÜnür2012年

3

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


2

1は2と3とは実質的に異なります。それは、配列をそのまま残し、他の2つは空のままにするためです。

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

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

undef @Array;

それが終わったら。

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

@Array = ();基になる配列を解放しません。範囲外であってもそれはできません。配下の配列を解放したい場合は、を使用しますundef @Array;
池上

2
デモ; 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
池上

何??? GCの全体のポイントは、かつては参照カウント== 0であると思っていましたが、関係するメモリはリサイクル可能になります。
CodeClown42

@ikegami:()vs のことはわかりますが、undefスコープの外に出ても、そのスコープのローカルの配列で使用されているメモリが解放されない場合、perlはリークの災害になりませんか?それ本当であるはずがない。
CodeClown42

彼らも漏れません。サブルーチンはまだそれらを所有しており、次にサブルーチンが呼び出されたときにそれらを再利用します。速度を最適化。
池上

1

要素または配列を出力する単一行。

$ array for(@array);を出力します。

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

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

出力:2 4 6


0

このような質問を決定してベンチマークする最良の方法:

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、サブバージョン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は、速度以外にもこれらの実装にはかなりの数の違いがあることも指摘しています。


1
は配列の長さではなく、最後のインデックスで$index < $#arrayある$index <= $#arrayため、実際に比較する必要があります$#array
josch
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.