James Goe
James Goe

Reputation: 522

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.

Upvotes: 2

Views: 331

Answers (2)

ikegami
ikegami

Reputation: 385655

# 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.

Upvotes: 14

Oktalist
Oktalist

Reputation: 14714

When people say they are used interchangably, they mean that the foreach keyword is just a synonym for the for keyword. That means the following two lines mean exactly the same thing:

foreach my $item (@array) {...}
for my $item (@array) {...}

For the avoidance of doubt I will ignore the foreach keyword and use only for below.

The following two lines, as you have seen, do not behave identically:

for my $item (@array) {...}
for (my $i = 0; $i < scalar @array; $i++) {...}

The manual advises against adding or removing elements in @array in the loop body of the first line, and doesn't say what would be the result.

If the loop body of the second line removes an element in @array at or before $i, the result will be that the element at new position $i will be skipped over. If it adds an element at or before $i, the result will be that the element at old position $i will be seen by the loop a second time.

Upvotes: 6

Related Questions