El problema es en el delete.
Efectivamente, si ejecutamos el código que pones, a mí tampoco me dá error. El problema es que al hacer el delete, lo que estamos haciendo es dejar como indefinidos los valores que hay dentro del array.
Me explico: si hacemos un delete de un elemento que esté al final del array, el elemento desaparece y el array se reduce en una unidad. Pero si el elemento está en mitad o al principio, lo que hace Perl es dejarlo en el estado anterior a ser inicializado. Si se hiciese un
exists($array[$i]) nos devolverá falso. Pero su posición dentro del array seguirá existiendo.
Para eliminar elementos dentro de un array, hay que usar
splice. Con
splice(@array,$i,1); estamos quitando el elemento i-ésimo del array.
Pero ahora tenemos otro problema. Supongamos que el array tiene 6 elementos, y que el elemento a eliminar está en la tercera posición. Al hacer el splice, lo eliminamos. El array se reduce a 5. El problema es que en el bucle for, al aumentar el valor del índice $i para apuntar al siguiente elemento, resulta que estamos apuntando al siguiente elemento de donde realmente deberíamos mirar (hemos quitado un elemento y el resto del array se han desplazado). Y un segundo problema: el bucle for sigue pensando que el array sigue teniendo 6 elementos. Nos sacará mensajes de error.
Una solución ingeniosa es... leer el array desde el final:
- Código: Seleccionar todo
1 #!/usr/bin/perl -l
2 use strict;
3 use warnings;
4 use diagnostics;
5
6 my @basura = qw(Y DEL DE LA LAS EL);
7 my $cadena = 'COMO ESTAN EL DIA DE HOY';
8
9 my @array = split(/ /,$cadena);
10 print "Original: ", join(" ", @array);
11 for ( my $i = $#array; $i >= 0; $i--) {
12 for my $j ( 0 .. $#basura) {
13 if( $array[$i] eq $basura[$j] ) {
14 splice(@array,$i,1);
15 last;
16 }
17 }
18 }
19 print "Resultado: ", join(" ", @array);
Es tu mismo código salvo algún cambio estético y, además, en la línea 11, recorremos el array desde atrás.
Yendo hacia atrás, en el caso de que tengamos que eliminar un elemento, no habrá problemas con el índice $i (porque siempre estamos apuntando al siguiente elemento a analizar) y tampoco con el problema de la longitud del array (siempre $i se dirige hacia el mismo valor, el 0).
De todas formas, se puede aprovechar esta situación para enseñar una técnica en la que se piensa poco: un bucle no es un caballo desbocado a galope. Podemos pedirle a nuestra máquina que repiense lo que está haciendo:
- Código: Seleccionar todo
1 #!/usr/bin/perl -l
2 use strict;
3 use warnings;
4
5 my @basura = qw(Y DEL DE LA LAS EL);
6 my $cadena = 'COMO ESTAN EL DIA DE HOY';
7
8 my @array = split(/ /,$cadena);
9 print "Original: ", join(" ", @array);
10
11 BUCLE:
12 for ( my $i = 0; $i < @array; $i++ ) {
13 for ( my $j = 0; $j < @basura; $j++ ) {
14 if( $array[$i] eq $basura[$j] ) {
15 splice(@array,$i,1);
16 redo BUCLE;
17 }
18 }
19 }
20 print "Resultado: ", join(" ", @array);
* En la línea 11, marco el bucle siguiente con una etiqueta.
* En la 12, es un bucle normal, pero aquí incluímos como test el actual valor del tamaño del array (número de elementos).
* Sigue todo normal hasta el momento del borrado del elemento. En la línea 16, como hemos cambiado la situación (un cambio en @array), le decimos que rehaga la comprobación del bucle BUCLE (
redo BUCLE). Es decir, Perl sale del bucle for interno y entra en el for externo, con el MISMO valor de $i, pero comprobando si ese valor de $i ha superado el límite del tamaño de @array.
Nosotros sabemos que si relanzamos el bucle con el mismo valor de $i, en esa vuelta estaremos chequeando un nuevo elemento de @array, porque hemos desplazado todos los elementos una posición desde el final, al eliminar antes el elemento i-ésimo, por lo que en esa posición estará un nuevo elemento.
redo se utiliza poco, pero viene muy bien para estos casos en los que las circunstancias y los elementos de los bucles cambian, y hay que volver a comprobar los test.
Finalmente, estudia esta otra versión:
- Código: Seleccionar todo
#!/usr/bin/perl -l
use strict;
use warnings;
my @basura = qw(Y DEL DE LA LAS EL);
my $cadena = 'COMO ESTAN EL DIA DE HOY';
my @verdad;
my @cadena = split(/ /,$cadena);
print "Original: ", join(" ", @cadena);
PALABRA:
foreach my $palabra ( @cadena ) {
foreach my $basura ( @basura ) {
next PALABRA if $palabra eq $basura; # Miramos la siguiente palabra si la actual es basura
}
push @verdad, $palabra; # Si no es basura, nos la guardamos
}
print "Resultado: ", join(" ", @verdad);
Interesante. En este hilo de discusión hemos tratado de los tres controles de bucles:
last,
next y
redo. Faltaría hablar del bloque
continue, pero ese sí que es más raro de ver...