web-dev-qa-db-fra.com

Quel est le meilleur moyen de supprimer une valeur d'un tableau en Perl?

Le tableau contient beaucoup de données et je dois supprimer deux éléments.

Ci-dessous l'extrait de code que j'utilise,

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

Utilisez splice si vous connaissez déjà l'index de l'élément à supprimer.

Grep fonctionne si vous recherchez.

Si vous devez en effectuer beaucoup, vous obtiendrez de bien meilleures performances si vous maintenez votre tableau dans un ordre trié, car vous pouvez alors effectuer une recherche binaire pour trouver l'index nécessaire.

Si cela vous semble judicieux, vous pouvez envisager d'utiliser une "valeur magique" pour les enregistrements supprimés, plutôt que de les supprimer, afin de sauvegarder les mouvements de données - définissez les éléments supprimés sur undef, par exemple. Naturellement, cela a ses propres problèmes (si vous avez besoin de connaître le nombre d'éléments "en direct", vous devez le suivre séparément, etc.), mais cela peut valoir la peine en fonction de votre application.

Edit En fait, maintenant que je jette un deuxième coup d'oeil - n'utilisez pas le code grep ci-dessus. Il serait plus efficace de trouver l'index de l'élément que vous voulez supprimer, puis utilisez splice pour le supprimer (le code que vous avez accumulé accumule tous les résultats ne correspondant pas.)

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

Cela supprimera la première occurrence. La suppression de toutes les occurrences est très similaire, sauf que vous voudrez obtenir tous les index en un seul passage:

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

Le reste est laissé comme un exercice pour le lecteur - rappelez-vous que le tableau change lorsque vous le raccordez!

Edit2 John Siracusa a correctement souligné que j'avais un bug dans mon exemple .. corrigé, désolé pour ça.

84
SquareCog

splice va supprimer les éléments du tableau par index. Utilisez grep, comme dans votre exemple, pour rechercher et supprimer.

13
spoulson

Est-ce quelque chose que vous allez faire beaucoup? Si tel est le cas, vous pouvez envisager une structure de données différente. Grep va chercher dans tout le tableau à chaque fois et un grand tableau pourrait être assez coûteux. Si la vitesse est un problème, vous pouvez envisager d'utiliser un hachage à la place.

Dans votre exemple, la clé serait le nombre et la valeur serait le nombre d'éléments de ce nombre.

8
tvanfosson

si vous changez

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

à

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

Cela évite le problème de renumérotation du tableau en supprimant d’abord les éléments à l’arrière du tableau. Mettre une épissure () dans une boucle foreach nettoie @arr. Relativement simple et lisible ...

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

Je pense que votre solution est la plus simple et la plus facile à gérer.

Le reste de l'article décrit la difficulté de transformer des tests sur des éléments en splice compensations. Ainsi, ce qui en fait une réponse plus complète .

Regardez les gyrations vous devez avoir un algorithme efficace (c.-à-d. En un seul passage) pour transformer les tests en liste. éléments dans les index. Et ce n'est pas si intuitif que ça.

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

Vous pouvez utiliser le découpage en matrice au lieu de l'épissage. Grep pour renvoyer les index que vous souhaitez conserver et utiliser le découpage en tranches:

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

J'utilise:

delete $array[$index];

Perldoc delete .

2
Ariel Monaco

Le meilleur que j'ai trouvé était une combinaison de "undef" et "grep":

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

Ça fait l'affaire! Federico

2
Federico

Supprimer toutes les occurrences de 'quelque chose' si array.

Basé sur les réponses 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);

Chaque fois que nous supprimons l'index de @arr, le prochain index correct à supprimer sera $_-current_loop_step.

2
Tom Lime

Vous pouvez utiliser le groupe sans capture et une liste d'éléments de délimitation de tuyaux à supprimer.


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

Juste pour être sûr d’avoir référencé les solutions grep et map, en recherchant d’abord les index des éléments correspondants (ceux à supprimer), puis en supprimant directement les éléments par grep sans rechercher les index. Il semble que la première solution proposée par Sam en posant sa question était déjà la plus rapide.

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

Le résultat est:

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

Comme le montrent les temps écoulés, il est inutile d'essayer d'implémenter une fonction de suppression en utilisant soit des index grep, soit des index définis par la carte. Juste grep-remove directement.

Avant de tester, je pensais que "map1" serait le plus efficace ... Je devrais plus souvent me fier à Benchmark, je suppose. ;-)

1
Gilles Maisonneuve

Un code similaire que j'ai écrit une fois pour supprimer des chaînes ne commençant pas par SB.1 d'un tableau de chaînes

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

Vous pouvez simplement faire ceci:

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

Si vous connaissez l'index du tableau, vous pouvez delete () . La différence entre splice () et delete () est que delete () ne renumérote pas les éléments restants du tableau.

0
Powerlord