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

Hi,

I have this simple test program to delete the first element of an array if it matches something then to continue processing but I loose the next element of the array! Please help
Code:
my @array = ("joby", "andy", "ben", "tom", "bob"); print "start\n"; for my $name (@array){ print "$name\n"; } print "\nmiddle\n"; foreach my $name (@array){ print "$name "; if($name eq 'joby'){ shift(@array); print "deleted\n"; next; } print "ok\n"; } print "\nend\n"; for my $name (@array){ print "$name\n"; }
Output:
C:\>test.pl<br> start<br> joby<br> andy<br> ben<br> tom<br> bob<br> <br> middle<br> joby deleted<br> ben ok<br> tom ok<br> bob ok<br> <br> end<br> andy<br> ben<br> tom<br> bob<br> <br> C:\><br> <br>
Expected Output:
C:\>test.pl<br> start<br> joby<br> andy<br> ben<br> tom<br> bob<br> <br> middle<br> joby deleted<br> andy ok<br> ben ok<br> tom ok<br> bob ok<br> <br> end<br> andy<br> ben<br> tom<br> bob<br> <br> C:\><br>

Replies are listed 'Best First'.
Re: Trouble with loops
by pc88mxer (Vicar) on Jul 01, 2008 at 01:37 UTC
    To remove an element of an array you have to splice it out. This means you have to find the first index of the matching element:
    for my $i (0..$#array) { if ($array[$i] eq "joby") { splice(@array, $i, 1); last; # exit loop - only interested in the first match } }
    Your use of shift(@array) removes the first element of the array, not the element you are currently examining.
Re: Trouble with loops
by almut (Canon) on Jul 01, 2008 at 06:03 UTC
    foreach my $name (@array){ print "$name "; if($name eq 'joby'){ shift(@array); print "deleted\n"; next; } print "ok\n"; }

    Your problem is that you're modifying the array you're iterating over.  Just don't do that with a foreach loop :)

    (This issue comes up from time to time, so if you search this site you should find rather extensive related discussion... (e.g. self-feeding infinite loop))

Re: Trouble with loops
by Narveson (Chaplain) on Jul 01, 2008 at 06:20 UTC

    To weed an array, use grep. To make your desired deletion from @array,

    @array = grep {$_ ne 'joby'} @array;

    To get your desired output while deleting, I recommend printf in the grep block:

    use strict; use warnings; my @array = ("joby", "andy", "ben", "tom", "bob"); sub ok {$_ ne 'joby'} print "start\n"; print map {"$_\n"} @array; print "\nmiddle\n"; my $fmt = "%s %s\n"; @array = grep { my $ok = ok(); printf $fmt, $_, $ok? 'ok': 'deleted'; $ok; } @array; print "\nend\n"; print map {"$_\n"} @array;
      Thanks for all the help guys, the real code is part of a huge loop processing all sorts of stuff, I'll do the check and modify the array before I hit the main loop.

      thanks again!
Re: Trouble with loops
by jwkrahn (Abbot) on Jul 01, 2008 at 05:47 UTC

    If you just want to test and remove the first element of an array then you don't need a loop:

    my @array = qw( joby andy ben tom bob ); print "start\n"; for my $name ( @array ) { print "$name\n"; } print "\nmiddle\n"; shift @array and print "deleted\n" if $array[ 0 ] eq 'joby'; print "\nend\n"; for my $name ( @array ) { print "$name\n"; }
      you don't need a loop

      I guess the program doesn't need to loop through an array if the program author has already looked the array over and seen that the stated criterion only applies to the first element. By the same reasoning, you don't need shift:

      my @array = qw( joby andy ben tom bob ); print "start\n"; for my $name ( @array ) { print "$name\n"; } print "\nmiddle\n"; print "deleted the first element because it was 'joby'\n"; @array = qw( andy ben tom bob ); print "\nend\n"; for my $name ( @array ) { print "$name\n"; }