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

Hi -

I'm trying to go through an array with a loop, test each element, and eliminate it from said array if it meets a certain condition. I'm using splice with a C-style for loop. However, the array always ends up empty (the array has 2 elements and should only be throwing out one). P.S. - the array is an array of array references.

my $i; for ($i = 0; $i < @un; $i++) { my $username = $un[$i]; my @username = @$username; $sth->execute($username[0]) or die $dbh->errstr; $uid = $sth->fetchrow_array; unless ($uid) { warn "User $username[0] missing.\n"; # remove element from array splice (@un, $i, 1); next; } $info{$uid} = $role; }

Is this because you can't splice an array while iterating over it in a loop? Other problem? Any thoughts appreciated.

Thanks,
AEB

  • Comment on How do I remove an element from any array while iterating over it in a loop?
  • Download Code

Replies are listed 'Best First'.
Re: How do I remove an element from any array while iterating over it in a loop?
by tachyon (Chancellor) on Aug 30, 2001 at 22:54 UTC

    This works fine and deletes any element equal to 5 while iterating over the loop. You are using this so you can infer that the unless($uid) conditional is always executed (meaning you never get a true value in $uid) thus you splice your array down to nothing. Add a print $uid debugging statement an I reckon you will find this is the case.

    $" = "~";
    
    @ary = qw( 5 1 2 3 4 5 6 7 8 9 5 );
    
    print "@ary\n";
    
    for my $i ( 0 .. $#ary ) {
        splice @ary, $i, 1 if $ary[$i] == 5;
    }
    
    print "@ary\n";
    

    Update - Senator Retracts earlier statement

    Hmm as pointed out by the ever vigilant MeowChow this does not work in some cases although it does on the test given. This will work. Proviso you will lose all undefined elements in your array.

    @ary = qw( 0 1 5 5 5 0 5 5 5 1 0 ); $" = "~"; print "@ary\n"; for my $i ( 0 .. $#ary ) { # undefine bad element undef $ary[$i] if $ary[$i] == 5; } # now remove undefined elements @ary = grep{ defined }@ary; print "@ary\n";

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      @ary = qw( 1 5 5 5 5 2 );
      Would you care to revise your statement, senator? :)
         MeowChow                                   
                     s aamecha.s a..a\u$&owag.print

        This is how the original should have been and -w would have well warned me that it was broke. You need to understand that splice will change the length of an array and so to the index of the next element when in a loop. Public humiliation *again* as MeowChow walks up, LOL and saunters off :-)

        my @ary = qw( 0 1 5 5 5 0 5 5 5 1 0 ); $" = "~"; print "@ary\n"; my $deletions = 0; for my $i ( 0 .. $#ary ) { if ($ary[$i-$deletions] == 5) { splice @ary, ($i-$deletions), 1; $deletions++; } } print "@ary\n";

        cheers

        tachyon

        s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: How do I remove an element from any array while iterating over it in a loop?
by IraTarball (Monk) on Aug 31, 2001 at 00:27 UTC
    grep is probably best here, as meowchow said. But now that I've said that, I think if you just decrement the index before doing the next you're code will do what you expect. Here's my snippet:
    use strict; use warnings; my @arry = qw ( 1 5 5 5 5 2 ); my $i = 0; print "@arry\n"; for ($i = 0; $i < @arry; $i++) { next unless $arry[$i] == 5; splice (@arry, $i, 1); --$i; } print "@arry\n";
    I personally think this is ugly and more difficult to follow than:
    use strict; use warnings; my @arry = qw ( 1 5 5 5 5 2 ); my $i = 0; print "@arry\n"; @arry = grep { $_ != 5 } @arry; print "@arry\n";
    but maybe that's just me.

    Ira,

    "So... What do all these little arrows mean?"
    ~unknown

Re: How do I remove an element from any array while iterating over it in a loop?
by wog (Curate) on Aug 31, 2001 at 00:44 UTC
    If you iterate through the array backwards you can avoid the need to adjust for array elements changing indicies, as you would have to iterating through it forwards:

    # ... for ($i = $#arry; $i >= 0; $i--) { next unless $arry[$i] == 5; splice @arry, $i, 1; # no need to adjust $i here, going backwards. }

    However, grep is probably better for this task.

Re: How do I remove an element from any array while iterating over it in a loop?
by John M. Dlugosz (Monsignor) on Aug 30, 2001 at 22:44 UTC
    Use map. @array= map { dostuff } @array; The block of code ("dostuff" here) is called once for each element with $_ aliased with that element. It returns $_ unaltered or returns an empty list to delete that element.

        Hmm, using grep, your "test" is the iteration code and returns true to keep, false to discard. Cute. Maybe too cute to understand later, though?

Re: How do I remove an element from any array while iterating over it in a loop?
by bless$self=>perlmonks; (Novice) on Aug 31, 2001 at 05:34 UTC
    Use redo instead of next inside your unless ($uid) {} block. When you splice out an element, the remaining elements are shifted down by 1. After the splice, $i has the index of the next element, but then the for loop increments it. redo skips the increment.