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

80

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;
user21246
quelle
3
Dadurch werden drei Elemente gelöscht.
Medlock Perlman
benötigt oben entfernen Sie alle nicht Datei Element Formular Verzeichnisliste und "Array = grep {-f $ _} Array" funktionierte wie ein Zauber für mich :)
Taiko

Antworten:

85

Verwenden Sie Spleiß, wenn Sie den Index des Elements, das Sie löschen möchten, bereits kennen.

Grep funktioniert, wenn Sie suchen.

Wenn Sie viele 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, sollten 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 "Live" -Elemente kennen müssen, müssen Sie diese separat verfolgen usw.), kann sich jedoch je nach Anwendung lohnen.

Bearbeiten Eigentlich jetzt, wo ich einen zweiten Blick darauf werfen werde - 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 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, außer 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 beim Spleißen ändert!

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

SquareCog
quelle
13
Wenn die Zeichenfolge nicht gefunden wird, bleibt die Schleife hängen, ebenso wie mein $ index = 0; mein $ count = scalar @arr; $ index ++ bis $ arr [$ index] eq 'foo' oder $ index == $ count; Spleiß (@arr, $ index, 1);
Amir.F
1
oder my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }- für das erste Spiel
Reflective
13

Spleiß entfernt Array-Elemente nach Index. Verwenden Sie grep wie in Ihrem Beispiel zum Suchen und Entfernen.

Spoulson
quelle
Danke Spoulson. Ich habe keine Indizes, die ich löschen muss, und musste daher auf grep zurückgreifen.
user21246
8

Wirst du das viel tun? In diesem Fall möchten Sie möglicherweise eine andere Datenstruktur in Betracht ziehen. Grep wird jedes Mal das gesamte Array durchsuchen und nach einem großen Array suchen, was ziemlich kostspielig sein kann. 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.

Tvanfosson
quelle
5

wenn du dich änderst

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 Umnummerierung des Arrays, indem zuerst Elemente von der Rückseite des Arrays entfernt werden. Wenn Sie einen Spleiß () in eine foreach-Schleife einfügen, wird @arr bereinigt. Relativ einfach und lesbar ...

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

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

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];
oryan_dunn
quelle
Mir gefällt besonders die Logik und Eleganz dieses Ansatzes.
Keve
Ja, Sie können es sogar als Einzeiler schreiben, @arr = @arr[grep ...]was mir besonders gefällt. Ich bin mir nicht sicher, wie effizient es ist, aber ich werde es verwenden, weil es nicht schlechter sein kann als die anderen Lösungen.
Soger
3

Ich denke, Ihre Lösung ist die einfachste und wartbarste.

Der Rest des Beitrags dokumentiert die Schwierigkeit, Tests an Elementen in spliceOffsets umzuwandeln. Dies macht es zu einer vollständigeren Antwort.

Schauen Sie sich die Drehungen an, die Sie durchlaufen müssen, um einen effizienten Algorithmus (dh einen Durchgang) zu erhalten, mit dem Tests für Listenelemente in Indizes umgewandelt werden können. 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;
}
Axeman
quelle
2
Ein einfaches "grep" wird viel einfacher zu verstehen und effizienter sein.
Randal Schwartz
5
Jemand hat meinen Kommentar gelöscht, dass Sie den Text eindeutig nicht gelesen haben.
Axeman
2

Ich benutze:

delete $array[$index];

Perldoc löschen .

Ariel Monaco
quelle
9
Löschen auf Array-Wert ist wahrscheinlich veraltet (siehe Ihr Dokument)
e2-e4
3
Dadurch wird nur der an diesem Array-Index gespeicherte Wert gelöscht. Zumindest in meiner Version von Perl, (5.14)
Rooster
Dies löscht NICHT wirklich, was Sie denken. Es löscht nur den Wert und macht ihn undef. Außerdem aus dem durch ringø verknüpften Dokument: "WARNUNG: Das Aufrufen von delete für Array-Werte wird dringend empfohlen. Das Löschen oder Überprüfen der Existenz von Perl-Array-Elementen ist konzeptionell nicht kohärent und kann zu überraschendem Verhalten führen." (Der vorherige Absatz im Dokument enthält alle wichtigen Details).
Mivk
2

Löschen Sie alle Vorkommen von 'etwas' wenn Array.

Basierend auf den Antworten von 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);

Jedes Mal, wenn wir den Index entfernen @arr, wird der nächste korrekte Index gelöscht $_-current_loop_step.

Tom Lime
quelle
2

Sie können die nicht zu erfassende Gruppe und eine Pipe-Delim-Liste 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"'
Reich
quelle
2

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

Federico
quelle
undef setzt den Elementwert auf null. Die Gesamtzahl der Elemente (Größe) bleibt unverändert.
Boontawee Home
1
@BoontaweeHome, der grepam Ende sie dann entfernt.
Deanna
1

Nur um sicherzugehen, dass ich Grep- und Map-Lösungen verglichen habe, indem ich zuerst nach Indizes übereinstimmender Elemente (die zu entfernender) gesucht und dann die Elemente direkt durch Grep entfernt habe, 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 verstrichenen Zeiten zeigen, ist es sinnlos zu versuchen, eine Entfernungsfunktion entweder mit grep- oder map-definierten Indizes zu implementieren. Einfach direkt grep-entfernen.

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

Gilles Maisonneuve
quelle
0

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

Powerlord
quelle
Ich meinte eigentlich Umnummerierung, was laut Perldoc splice () tut.
Powerlord
0

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);}
}
BBT
quelle
0

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";
Chetan
quelle