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

i want to remove all zeroes from @arry using splice but i can't get rid of all zeroes, what is wrong.
use strict; use warnings; my @arry = (9,0,0,5,3,0,0,0,2,0,1,0); print "@arry \n"; my $i = 0; while ( $i <= $#arry) { if ($arry[$i] == 0) {splice @arry, $i,1;} $i++ ; } print "@arry \n";
the result will be
9 0 0 5 3 0 0 0 2 0 1 0
9 0 5 3 0 2 1

Replies are listed 'Best First'.
Re: can't remove all zeroes from an array
by GrandFather (Saint) on Aug 09, 2007 at 08:25 UTC

    The problem is that as you remove elements you invalidate the index that you are using. However, Perl has a much better way of doing what you want. Consider:

    use strict; use warnings; my @arry = (9,0,0,5,3,0,0,0,2,0,1,0); print "@arry \n"; @arry = grep {$_} @arry; print "@arry \n";

    Prints:

    9 0 0 5 3 0 0 0 2 0 1 0 9 5 3 2 1

    The magic is grep.


    DWIM is Perl's answer to Gödel
Re: can't remove all zeroes from an array
by johngg (Canon) on Aug 09, 2007 at 09:06 UTC
    GrandFather has pointed out the best solution to your problem using grep and the reason why your code was failing. However, it is sometimes a useful technique, when doing something that might invalidate an index, to consider working back from the end of the array to the beginning. That way you can avoid mucking the index up.

    use strict; use warnings; my @arr = (9, 0, 0, 5, 3, 0, 0, 0, 2, 0, 1, 0); print qq{@arr\n}; for my $idx ( reverse 0 .. $#arr ) { splice @arr, $idx, 1 unless $arr[$idx]; } print qq{@arr\n};

    This produces

    9 0 0 5 3 0 0 0 2 0 1 0 9 5 3 2 1

    I hope this is useful.

    Cheers,

    JohnGG

Re: can't remove all zeroes from an array
by Beechbone (Friar) on Aug 09, 2007 at 08:41 UTC
    When your code removes a 0, another element of the array will slide into that slot---but your code will move on to the next slot and will not check that element. So if there are 2 0s in a row, it will check and remove the first 0 and ignore the second 0 because that now is at a position where the first 0 was already removed.

    BTW: This is exactly the same kind of problem a for loop will run into if you change the array it is running over. So remember the old rule to never change an array you're iterating over unless you know exactly what you are doing.

    Your algo is not that far of, just put the $i++ in an else block. (Or use a more perlish way, see the othe reply.)


    Search, Ask, Know
Re: can't remove all zeroes from an array
by ikegami (Patriarch) on Aug 09, 2007 at 12:55 UTC
    The smallest change needed to fix your code is to only increment $i when you don't remove an element.
    while ( $i <= $#arry) { if ($arry[$i] == 0) {splice @arry, $i,1;} else {$i++;} }
      Or, alternatively, move backwards.

      When you splice like this, items with a larger index shift forward, but those with a lower index remain in place — so the problem simply vanishes by looping back to front.

      $i = @arry; while ( --$i >= 0 ) { if ($arry[$i] == 0) {splice @arry, $i,1;} }
Re: can't remove all zeroes from an array
by FunkyMonk (Bishop) on Aug 09, 2007 at 08:50 UTC
    Consider what happens before the splice, when $i is 1:
    • Before the splice:
      9,0,0,5,3,0,0,0,2,0,1,0 ^ $i is 1

    • After the splice:
      9,0,5,3,0,0,0,2,0,1,0 ^ $i is 1

      $arry[$i] is still 0

    You need to keep spliceing while it remains zero, change your if to a while.

    Now think about what happens when you've just spliced that last 0. $i points past the end of the array, so you need to add a check for that into the while condition:

    while ( $i <= $#arry && $arry[$i] == 0 )

    Having said all of that, GrandFather is right. Just use grep.

Re: can't remove all zeroes from an array
by Anno (Deacon) on Aug 09, 2007 at 09:34 UTC
    As has been noted, the standard solution would be to use grep().

    The problem you encountered with splice() is very common: Splicing elements in or out of a list upsets the numbering of the remaining elements. Often the solution is to traverse the list in reverse order, beginning at the high end. Thus, splice can be used much in the way you originally had it:

    my $i = $#arry; while ( $i >= 0 ) { if ($arry[$i] == 0) {splice @arry, $i,1;} $i-- ; }
    or, more compact:
    $arry[ $_] or splice @arry, $_, 1 for reverse 0 .. $#arry;
    Anno
Re: can't remove all zeroes from an array
by swampyankee (Parson) on Aug 09, 2007 at 14:29 UTC

    Praveen, ikegami, Grandfather, etc all had wonderful ideas.

    I'd use grep, but my code would probably look like this:

    @array = (1, 0, 2, 0, 3, -1, 0, 4, 0, -5, 0, 123); @nonzeros = grep {$_} @array;

    or

    @array = (1, 0, 2, 0, 3, -1, 0, 4, 0, -5, 0, 123); @nonzeros = grep {$_ != 0} @array;

    Both are based on the assumption that the array's contents are strictly numeric; if you want to remove strings, your grep would require a different regex.


    emc

    Information about American English usage here and here.

    Any New York City or Connecticut area jobs? I'm currently unemployed.

Re: can't remove all zeroes from an array
by Praveen (Friar) on Aug 09, 2007 at 11:23 UTC
    Try This
    my @arry = (9,0,0,5,3,0,0,0,2,0,1,0); print "@arry \n"; @new_ary = grep{/[^0]/} @arry; print "@new_ary \n";