it-swarm.com.de

Was ist der beste Weg, um einen Wert aus einem Array in Perl zu löschen?

Das Array hat viele Daten und ich muss zwei Elemente löschen.

Unten ist das Code-Snippet, das ich verwende,

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

Verwenden Sie Splice, wenn Sie den Index des zu löschenden Elements bereits kennen.

Grep funktioniert, wenn Sie suchen.

Wenn Sie eine Menge davon ausführen müssen, erhalten Sie eine viel bessere Leistung, wenn Sie Ihr Array in sortierter Reihenfolge halten, da Sie dann eine binäre Suche durchführen können, um den erforderlichen Index zu finden.

Wenn es in Ihrem Kontext sinnvoll ist, können Sie einen "magischen Wert" für gelöschte Datensätze verwenden, anstatt sie zu löschen, um Datenverschiebungen zu vermeiden. Setzen Sie beispielsweise gelöschte Elemente auf Undef. Dies hat natürlich seine eigenen Probleme (wenn Sie die Anzahl der "aktiven" Elemente kennen müssen, müssen Sie diese separat nachverfolgen usw.), kann sich jedoch je nach Ihrer Anwendung als lohnend erweisen.

Edit Eigentlich jetzt, da ich einen zweiten Blick darauf werfen - benutze nicht den obigen Grep-Code. Es wäre effizienter, den Index des Elements zu finden, das Sie löschen möchten, und ihn dann mit dem Spleiß zu löschen (der Code, den Sie haben, sammelt alle nicht übereinstimmenden Ergebnisse).

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

Dadurch wird das erste Vorkommen gelöscht. Das Löschen aller Vorkommen ist sehr ähnlich, mit der Ausnahme, dass Sie alle Indizes in einem Durchgang erhalten möchten:

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

Der Rest bleibt als Übung für den Leser - denken Sie daran, dass sich das Array ändert, wenn Sie es verbinden!

Edit2 John Siracusa hat richtig darauf hingewiesen, dass ich einen Fehler in meinem Beispiel hatte.

84
SquareCog

Splice entfernt Array-Elemente nach Index. Verwenden Sie grep wie in Ihrem Beispiel, um zu suchen und zu entfernen.

13
spoulson

Ist das etwas, was Sie viel tun werden? In diesem Fall möchten Sie möglicherweise eine andere Datenstruktur berücksichtigen. Grep wird jedes Mal das gesamte Array durchsuchen und nach einem großen Array zu suchen, kann ziemlich kostspielig sein. Wenn Geschwindigkeit ein Problem ist, sollten Sie stattdessen einen Hash verwenden.

In Ihrem Beispiel wäre der Schlüssel die Zahl und der Wert die Anzahl der Elemente dieser Zahl.

8
tvanfosson

wenn Sie sich ändern

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

zu

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

Dies vermeidet das Problem der Neunummerierung des Arrays, indem zuerst Elemente von der Rückseite des Arrays entfernt werden. Wenn Sie einen Splice () in eine foreach-Schleife einfügen, wird @arr bereinigt. Relativ einfach und lesbar ...

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

Ich denke, Ihre Lösung ist die einfachste und am besten wartbare.

Der Rest des Beitrags dokumentiert die Schwierigkeit, Tests an Elementen in splice Offsets umzuwandeln. Auf diese Weise erhalten Sie eine vollständigere Antwort .

Schauen Sie sich die Kreisel an Sie müssen einen effizienten (dh einmaligen) Algorithmus durchlaufen, um die Tests auf die Liste zu setzen Elemente in Indizes. Und es ist überhaupt nicht so intuitiv.

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;
}
3
Axeman

Sie können Array-Slicing anstelle von Spleißen verwenden. Grep, um die Indizes zurückzugeben, die Sie behalten möchten, und Slicing zu verwenden:

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

Ich benutze:

delete $array[$index];

Perldoc delete .

2
Ariel Monaco

Das Beste, was ich gefunden habe, war eine Kombination aus "undef" und "grep":

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

Das macht den Trick! Federico

2
Federico

Löschen Sie alle Vorkommen von 'something' if array.

Basierend auf den SquareCog-Antworten:

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

Jedes Mal, wenn wir den Index aus @arr Entfernen, lautet der nächste zu löschende Index $_-current_loop_step.

2
Tom Lime

Sie können die nicht erfassende Gruppe und eine Pipe-Begrenzungsliste der zu entfernenden Elemente verwenden.


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

Um sicherzugehen, dass ich grep- und map-Lösungen als Benchmark verwendet habe, habe ich zuerst nach Indizes übereinstimmender Elemente gesucht (die zu entfernen sind) und dann die Elemente direkt mit grep entfernt, ohne nach den Indizes zu suchen. Ich scheine, dass die erste von Sam vorgeschlagene Lösung, als er seine Frage stellte, bereits die schnellste war.

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

Das Ergebnis ist:

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

Wie die abgelaufenen Zeiten zeigen, ist es sinnlos, eine Entfernungsfunktion mithilfe von grep- oder map-definierten Indizes zu implementieren. Einfach direkt entfernen.

Vor dem Testen dachte ich, "map1" wäre am effizientesten ... Ich sollte mich häufiger auf Benchmark verlassen, denke ich. ;-)

1

Ein ähnlicher Code, den ich einmal geschrieben habe, um Zeichenfolgen, die nicht mit SB.1 beginnen, aus einem Array von Zeichenfolgen zu entfernen

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
BBT

Sie können dies einfach tun:

my $input_Color = 'Green';
my @array = qw(Red Blue Green Yellow Black);
@array = grep {!/$input_Color/} @array;
print "@array";
0
Chetan

Wenn Sie den Array-Index kennen, können Sie delete () it. Der Unterschied zwischen splice () und delete () besteht darin, dass delete () die verbleibenden Elemente des Arrays nicht neu nummeriert.

0
Powerlord