Perlで配列から値を削除する最良の方法は何ですか?


81

配列には大量のデータがあり、2つの要素を削除する必要があります。

以下は私が使用しているコードスニペットです、

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;

3
これにより、3つの要素が削除されます。
メドロックパールマン2015

必要なトップは、ディレクトリリストからすべての非ファイルアイテムを削除し、「array = grep {-f $ _} array」は私にとって魅力のように機能しました:)
taiko 2017年

回答:


87

削除する要素のインデックスがすでにわかっている場合は、スプライスを使用します。

検索している場合、Grepは機能します。

これらの多くを実行する必要がある場合は、配列をソートされた順序で保持すると、バイナリ検索を実行して必要なインデックスを見つけることができるため、パフォーマンスが大幅に向上します。

コンテキストで意味がある場合は、データの移動を節約するために、削除されたレコードを削除するのではなく、「魔法の値」を使用することを検討することをお勧めします。たとえば、削除された要素をundefに設定します。当然、これには独自の問題があります(「ライブ」要素の数を知る必要がある場合、個別に追跡する必要がある場合など)が、アプリケーションによっては問題の価値がある場合があります。

編集実際にもう一度見てみると、上記のgrepコードは使用しないでください。削除する要素のインデックスを見つけてから、スプライスを使用して削除する方が効率的です(コードによって一致しない結果がすべて蓄積されます)。

my $index = 0;
$index++ until $arr[$index] eq 'foo';
splice(@arr, $index, 1);

これにより、最初のオカレンスが削除されます。すべてのオカレンスを削除することは非常に似ていますが、1回のパスですべてのインデックスを取得する必要がある点が異なります。

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

残りは読者のための演習として残されています-配列をつなぎ合わせると配列が変わることを忘れないでください!

Edit2 John Siracusaは、私の例にバグがあることを正しく指摘しました。修正しました。申し訳ありません。


13
文字列が見つからない場合、ループがスタックするので、$ index = 0を実行します。私の$ count =スカラー@arr; $ arr [$ index] eq'foo 'または$ index == $ countまでの$ index ++; splice(@ arr、$ index、1);
Amir.F 2013年

1
またはmy ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }-最初の試合の場合
リフレクティブ

13

スプライスは、インデックスによって配列要素を削除します。例のように、grepを使用して検索および削除します。


spoulsonに感謝します。削除する必要のあるインデックスがないため、grepを使用する必要がありました。
user21246 2008年

8

これはあなたがたくさんやろうとしていることですか?その場合は、別のデータ構造を検討することをお勧めします。Grepは毎回配列全体を検索するため、大きな配列を検索するにはかなりのコストがかかる可能性があります。速度が問題になる場合は、代わりにハッシュの使用を検討することをお勧めします。

あなたの例では、キーは数値であり、値はその数値の要素の数です。


5

変更した場合

my @del_indexes = grep { $arr[$_] eq 'foo' } 0..$#arr;

my @del_indexes = reverse(grep { $arr[$_] eq 'foo' } 0..$#arr);

これにより、最初に配列の背面から要素が削除されるため、配列の番号変更の問題が回避されます。splice()をforeachループに入れると、@ arrがクリーンアップされます。比較的シンプルで読みやすい...

foreach $item (@del_indexes) {
   splice (@arr,$item,1);
}

5

スプライシングの代わりに配列スライシングを使用できます。保持してスライスを使用するインデックスを返すGrep:

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];

私はこのアプローチの論理と優雅さが特に好きです。
Keve 2018年

はい、確かに、あなたはそれを次のようなワンライナーとして書くことさえできます:@arr = @arr[grep ...]私は特に好きです。それがどれほど効率的かはわかりませんが、他のソリューションよりも悪くなることはないので、使い始めます。
soger

3

私はあなたの解決策が最も単純で最も保守しやすいと思います。

投稿の残りの部分では、要素のテストをspliceオフセットに変換することの難しさについて説明しています。したがって、それをより完全な答えにします。

リストアイテムのテストをインデックスに変換するための効率的な(つまり、ワンパス)アルゴリズムを使用するために通過する必要のある回転を見てください。そして、それはまったく直感的ではありません。

sub array_remove ( \@& ) { 
    my ( $arr_ref, $test_block ) = @_;
    my $sp_start  = 0;
    my $sp_len    = 0;
    for ( my $inx = 0; $inx <= $#$arr_ref; $inx++ ) {
        local $_ = $arr_ref->[$inx];
        next unless $test_block->( $_ );
        if ( $sp_len > 0 && $inx > $sp_start + $sp_len ) {
            splice( @$arr_ref, $sp_start, $sp_len );
            $inx    = $inx - $sp_len;
            $sp_len = 0;
        }
        $sp_start = $inx if ++$sp_len == 1;
    }
    splice( @$arr_ref, $sp_start, $sp_len ) if $sp_len > 0;
    return;
}

2
単純な「grep」は、それよりもはるかに理解しやすく、効率的です。
ランダルシュワルツ

5
あなたが明らかにテキストを読んでいないという私のコメントを誰かが削除しました。
Axeman

2

私が使う:

delete $array[$index];

Perldoc削除


9
配列値の削除は非推奨になる可能性があります(ドキュメントを参照)
e2-e4 2013

3
これは、その配列インデックスに格納されている値を削除するだけです。少なくとも私のバージョンのperlでは(5.14)
Rooster

これはあなたの考えを実際に削除するものではありません。値を削除するだけで、になりますundef。さらに、ringøによってリンクされたドキュメントから:「警告:配列値に対してdeleteを呼び出すことは強くお勧めしません。Perl配列要素の存在を削除またはチェックするという概念は概念的に一貫しておらず、驚くべき動作につながる可能性があります。」(ドキュメントの前の段落には、すべての厄介な詳細があります)。
mivk 2017

2

配列の場合、「何か」の出現をすべて削除します。

SquareCogの回答に基づく:

my @arr = ('1','2','3','4','3','2', '3','4','3');
my @dix = grep { $arr[$_] eq '4' } 0..$#arr;
my $o = 0;
for (@dix) {
    splice(@arr, $_-$o, 1);
    $o++;
}
print join("\n", @arr);

からインデックスを削除するたび@arrに、次に削除する正しいインデックスはになります$_-current_loop_step


2

非キャプチャグループとアイテムのパイプデリムリストを使用して削除できます。


perl -le '@ar=(1 .. 20);@x=(8,10,3,17);$x=join("|",@x);@ar=grep{!/^(?:$x)$/o} @ar;print "@ar"'

2

私が見つけた最高のものは、「undef」と「grep」の組み合わせでした。

foreach $index ( @list_of_indexes_to_be_skiped ) {
      undef($array[$index]);
}
@array = grep { defined($_) } @array;

それがトリックです!フェデリコ


undefは要素値をnullに設定します。合計要素(サイズ)は同じです。
ブーンタウィーホーム2016

1
@BoontaweeHome、grep最後にそれらを削除します。
Deanna 2017年

1

grepとマップのソリューションのベンチマークを行ったことを確認するために、最初に一致する要素(削除する要素)のインデックスを検索し、次にインデックスを検索せずにgrepによって要素を直接削除します。サムが質問したときに提案した最初の解決策は、すでに最速だったようです。

    use Benchmark;
    my @A=qw(A B C A D E A F G H A I J K L A M N);
    my @M1; my @G; my @M2;
    my @Ashrunk;
    timethese( 1000000, {
      'map1' => sub {
          my $i=0;
          @M1 = map { $i++; $_ eq 'A' ? $i-1 : ();} @A;
      },
      'map2' => sub {
          my $i=0;
          @M2 = map { $A[$_] eq 'A' ? $_ : () ;} 0..$#A;
      },
      'grep' => sub {
          @G = grep { $A[$_] eq 'A' } 0..$#A;
      },
      'grem' => sub {
          @Ashrunk = grep { $_ ne 'A' } @A;
      },
    });

結果は次のとおりです。

Benchmark: timing 1000000 iterations of grem, grep, map1, map2...
  grem:  4 wallclock secs ( 3.37 usr +  0.00 sys =  3.37 CPU) @ 296823.98/s (n=1000000)
  grep:  3 wallclock secs ( 2.95 usr +  0.00 sys =  2.95 CPU) @ 339213.03/s (n=1000000)
  map1:  4 wallclock secs ( 4.01 usr +  0.00 sys =  4.01 CPU) @ 249438.76/s (n=1000000)
  map2:  2 wallclock secs ( 3.67 usr +  0.00 sys =  3.67 CPU) @ 272702.48/s (n=1000000)
M1 = 0 3 6 10 15
M2 = 0 3 6 10 15
G = 0 3 6 10 15
Ashrunk = B C D E F G H I J K L M N

経過時間で示されているように、grepまたはmapで定義されたインデックスを使用してremove関数を実装しようとしても意味がありません。直接grep-removeするだけです。

テストする前は、「map1」が最も効率的だと思っていました...私はもっと頻繁にベンチマークに頼るべきだと思います。;-)


0

配列インデックスがわかっている場合は、それをdelete()できます。splice()とdelete()の違いは、delete()が配列の残りの要素に番号を付け直さないことです。


私は実際には番号の付け直しを意味しましたが、Perldocによれば、splice()はそうします。
パワーロード2008年

0

文字列の配列からSB.1で始まらない文字列を削除するために私がかつて書いた同様のコード

my @adoSymbols=('SB.1000','RT.10000','PC.10000');
##Remove items from an array from backward
for(my $i=$#adoSymbols;$i>=0;$i--) {  
    unless ($adoSymbols[$i] =~ m/^SB\.1/) {splice(@adoSymbols,$i,1);}
}

0

あなたは簡単にこれを行うことができます:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.