young_stu has asked for the wisdom of the Perl Monks concerning the following question:

Suppose I have an array
my @a
and this array contains some elements that I want to get rid of. Further, I don't know which indices of the array contain the elements I want to expunge, and I want the final array I end up with to contain no empty indices--i.e. I want to ``shorten" the array by 1 everytime I expunge the contents of any of its indices. The way to do this that strikes me as obvious seems inelegant:
my @new_a; for my $i (0..$#a){ push (@new_a, $a[$i]) unless is_unwanted($a[$i]); } @a = @new_a; @new_a = ();
where is_unwanted is a sub that tests to find out if the contents of an index are of a sort I want to get rid of. Is there a way to do this without creating this extra @new_a array? A (naive) use of splice won't work, since splicing an array changes the identity of the indices with each call. E.g.
my @a = ("bob", "bob", "martha", "bob"); for my $i (0..$#a){ splice (@a, $i, 1) if $a[$i] =~ m/bob/; } #@a is now ("bob", "martha"), but I don't want any "bob"s; #also I get harassed about uniniatialized values when I use warnings
Thanks!

Replies are listed 'Best First'.
Re: better array plucking?
by revdiablo (Prior) on May 10, 2005 at 21:55 UTC

    This is what grep was designed to do:

    @a = grep { not is_unwanted($_) } @a;

    Update: and if you really need to modify the array in place, using splice, then you can iterate on the indices in reverse:

    my @a = qw(bob bob alice bob john); for (reverse 0 .. $#a) { splice @a, $_, 1 if $a[$_] eq "bob"; }
Re: better array plucking?
by sh1tn (Priest) on May 10, 2005 at 22:28 UTC
    Rethink your technique, you may find HASH structure more suitable:
    my @a = ("bob", "bob", "martha", "bob"); my %a; @a{@a} = (); print keys %a;


Re: better array plucking?
by TedPride (Priest) on May 11, 2005 at 03:59 UTC
    Splice is much more efficient, and the only reason it isn't working here is you're skipping the item after each item you remove. The following works fine:
    my @c = (1..20); for ($i = 0; $i <= $#c; $i++) { if ($c[$i] >= 5 && $c[$i] <= 10) { splice(@c,$i,1); $i--; } } print join ' ', @c;
    Outputs:
    1 2 3 4 11 12 13 14 15 16 17 18 19 20
    Or for your example:
    my @c = ("bob", "bob", "martha", "bob"); for (my $i = 0; $i <= $#c; $i++) { if ($c[$i] eq "bob") { splice(@c,$i,1); $i--; } } print join ' ', @c;