¿Cuál es la mejor manera de eliminar un valor de una matriz en Perl?

81

La matriz tiene muchos datos y necesito eliminar dos elementos.

A continuación se muestra el fragmento de código que estoy usando,

my @array = (1,2,3,4,5,5,6,5,4,9);
my $element_omitted = 5;
@array = grep { $_ != $element_omitted } @array;
user21246
fuente
3
Esto elimina tres elementos.
Medlock Perlman
es necesario eliminar todas las listas de directorios de formularios de elementos que no son de archivo y "array = grep {-f $ _} array" funcionó como un encanto para mí :)
taiko

Respuestas:

87

Utilice empalme si ya conoce el índice del elemento que desea eliminar.

Grep funciona si estás buscando.

Si necesita hacer muchos de estos, obtendrá un rendimiento mucho mejor si mantiene su matriz en orden ordenado, ya que luego puede hacer una búsqueda binaria para encontrar el índice necesario.

Si tiene sentido en su contexto, es posible que desee considerar el uso de un "valor mágico" para los registros eliminados, en lugar de eliminarlos, para ahorrar en el movimiento de datos; por ejemplo, establezca los elementos eliminados en undef. Naturalmente, esto tiene sus propios problemas (si necesita saber el número de elementos "en vivo", debe realizar un seguimiento de ellos por separado, etc.), pero puede valer la pena según su aplicación.

Editar En realidad, ahora que echo un segundo vistazo, no use el código grep anterior. Sería más eficiente encontrar el índice del elemento que desea eliminar, luego use splice para eliminarlo (el código que tiene acumula todos los resultados que no coinciden ..)

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

Eso eliminará la primera aparición. Eliminar todas las apariciones es muy similar, excepto que querrá obtener todos los índices en una sola pasada:

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

El resto se deja como ejercicio para el lector; recuerde que la matriz cambia a medida que la empalma.

Edit2 John Siracusa señaló correctamente que tenía un error en mi ejemplo ... arreglado, lo siento.

SquareCog
fuente
13
si no se encuentra la cadena, el bucle se atascará, así que mi $ index = 0; my $ count = scalar @arr; $ index ++ hasta $ arr [$ index] eq 'foo' o $ index == $ count; empalme (@arr, $ índice, 1);
Amir.F
1
o my ($index) = grep { $arr[$_] eq 'foo' } 0..$#arr; if (defined $index) {splice(@arr, $index, 1); }- para el primer partido
Reflexivo
13

splice eliminará los elementos de la matriz por índice. Utilice grep, como en su ejemplo, para buscar y eliminar.

Spoulson
fuente
Gracias, spoulson. No tengo los índices que tengo que borrar y tuve que recurrir a grep.
user21246
8

¿Es esto algo que vas a hacer mucho? Si es así, es posible que desee considerar una estructura de datos diferente. Grep buscará en toda la matriz cada vez y una matriz grande podría ser bastante costosa. Si la velocidad es un problema, es posible que desee considerar usar un Hash en su lugar.

En su ejemplo, la clave sería el número y el valor sería el recuento de elementos de ese número.

tvanfosson
fuente
5

si cambias

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

a

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

Esto evita el problema de la renumeración de la matriz eliminando primero los elementos de la parte posterior de la matriz. Poner un empalme () en un bucle foreach limpia @arr. Relativamente simple y legible ...

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

Puede usar el corte de matriz en lugar de empalmar. Grep para devolver los índices que desea conservar y usar el corte:

my @arr = ...;
my @indicesToKeep = grep { $arr[$_] ne 'foo' } 0..$#arr;
@arr = @arr[@indiciesToKeep];
oryan_dunn
fuente
Particularmente me gusta la lógica y la elegancia de este enfoque.
Keve
Sí, de hecho, incluso puedes escribirlo en una sola línea como: @arr = @arr[grep ...]que me gusta particularmente. No estoy seguro de cuán eficiente es, pero voy a empezar a usarlo porque no puede ser peor que las otras soluciones.
soger
3

Creo que su solución es la más simple y fácil de mantener.

El resto de la publicación documenta la dificultad de convertir las pruebas de elementos en splicecompensaciones. Por lo tanto, haciéndolo más completo respuesta .

Mire los giros por los que tiene que pasar para tener un algoritmo eficiente (es decir, de una pasada) para convertir las pruebas en los elementos de la lista en índices. Y no es tan intuitivo en absoluto.

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
fuente
2
Un simple "grep" será mucho más fácil de entender y más eficiente que eso.
Randal Schwartz
5
Alguien borró mi comentario de que claramente no leíste el texto.
Axeman
2

Yo suelo:

delete $array[$index];

Eliminar Perldoc .

Ariel Mónaco
fuente
9
Es probable que la eliminación en el valor de la matriz quede obsoleta (consulte su documento)
e2-e4
3
esto simplemente elimina el valor almacenado en ese índice de matriz. al menos en mi versión de perl, (5.14)
Rooster
Esto realmente NO borra lo que piensas. Solo borra el valor, haciéndolo undef. Además, del documento vinculado por ringø: "ADVERTENCIA: Se desaconseja encarecidamente llamar a delete en valores de matriz. La noción de eliminar o verificar la existencia de elementos de matriz de Perl no es conceptualmente coherente y puede conducir a un comportamiento sorprendente". (el párrafo anterior en el documento tiene todos los detalles sangrientos).
mivk
2

Elimina todas las apariciones de 'algo' si matriz.

Basado en las respuestas de 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);

Cada vez que eliminemos un índice de @arr, el siguiente índice correcto para eliminar será $_-current_loop_step.

Tom Lime
fuente
2

Puede utilizar el grupo de no captura y una lista de elementos delimitados por tubería para eliminar.


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

Lo mejor que encontré fue una combinación de "undef" y "grep":

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

¡Eso hace el truco! Federico

Federico
fuente
undef establece el valor del elemento en nulo. Los elementos totales (tamaño) siguen siendo los mismos.
Boontawee Home
1
@BoontaweeHome, grepal final, los elimina.
Deanna
1

Solo para estar seguro de haber comparado las soluciones grep y map, primero buscando índices de elementos coincidentes (los que se deben eliminar) y luego eliminando directamente los elementos mediante grep sin buscar los índices. Parece que la primera solución propuesta por Sam al hacer su pregunta ya era la más rápida.

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

El resultado es:

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

Como lo muestran los tiempos transcurridos, es inútil intentar implementar una función de eliminación utilizando índices definidos por grep o por mapas. Simplemente grep-remove directamente.

Antes de probar, pensaba que "map1" sería el más eficiente ... Supongo que debería confiar más en Benchmark. ;-)

Gilles Maisonneuve
fuente
0

Si conoce el índice de la matriz, puede eliminarlo () . La diferencia entre splice () y delete () es que delete () no vuelve a numerar los elementos restantes de la matriz.

Señor de poder
fuente
De hecho, quise decir renumerar, que según Perldoc, splice () hace.
Powerlord
0

Un código similar que escribí una vez para eliminar cadenas que no comienzan con SB.1 de una matriz de cadenas

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
fuente
0

Simplemente puede hacer esto:

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