James Goe James Goe - 4 months ago 11
Perl Question

Perl: Why does for and foreach give me different results?

I looked online and everyone seems to say they are used interchangeably. However, it gave me different results when I was iterating through my array.

I had an array of approx. 60 string elements and were filtering out whatever matched in another array. But when I used

foreach
, it only saw an array of 45 elements so when I printed the final result, it did not filter everything out.

It was only when I used
for
that it worked as I wanted. What is the reason behind this?

I managed to solve the problem, now I just want to understand the way this works. Thanks!

EDIT: So i didn't provide any examples so here's what I did:

my $i = 0;
foreach my $item (@array){
print "\n$i : $item";
foreach my $exclude_item (@exclude){
chomp $exclude_item;
if ($exclude_item eq $item){
print "\nDEBUG: Removing $item";
splice @array, $i, 1;
}
}
$i++;
}


The above wasn't giving me a complete list of
@array
so when I printed the final result, there were elements inside that should've been filtered out.

So I tried this instead (which worked as I wanted):

for (my $i=0; $i < scalar @array; $i++){
print "\n$i : $array[$i]";
foreach my $exclude_item (@exclude){
chomp $exclude_item;
if ($exclude_item eq $array[$i]){
print "\nDEBUG: Removing $array[$i]";
splice @array, $i, 1;
}
}
}


Just additional info, the
@array
was created by searching for list of subdirectories in multiple folders. It's possible that
@array
contained duplicates.

Answer
# Foreach loop                               \           
foreach my $item (@array)                     > same     \
for my $item (@array)                        /            \
                                                           > different
# Augmented while loop                       \            /
for (my $i=0; $i < scalar @array; $i++)       > same     /
foreach (my $i=0; $i < scalar @array; $i++)  /

You must not modify an array over which you iterating. From perlsyn,

If any part of LIST is an array, foreach will get very confused if you add or remove elements within the loop body, for example with splice. So don't do that.

Therefore,

foreach (@array){
   ...
   splice @array, $i, 1;   # XXX Error
   ...
}

Note that you may safely modify the elements of an array over which are you iterating.

foreach (@array){
   $_ = uc($_);  # ok
}

Finally, a far better solution:

chomp(@exclude);

my %exclude = map { $_ => 1 } @exclude;

@array = grep { !$exclude{$_} } @array;

Aside from being much simpler, it scales much much better[1].


  1. It executes in O(E+A) time, whereas the OP's executed in O(E * A2) time.