Different results iterating over a hash in Perl

My problem is simple. I have to print some key-value pairs from a hash. I tried two different methods:

METHOD A

#Fill hash from file ($fh has been defined previously)

while(my $line = <$fh>) {
    $counter{$line}++;
}

foreach (my($k, $v) = each %counter){
    my ($p1, $p2) = split(/$;/o, $k);

    chomp $p2;

    if($v > 0){
      printf "%s_down_%s %s %d\n", $prefix, $p1, $p2, $v;
      printf "%s_up_%s %s %d\n", $prefix, $p2, $p1, $v;
    }
}

METHOD B

#Fill hash from file ($fh has been defined previously)

while(my $line = <$fh>) {
    $counter{$line}++;
}

foreach $k (keys %counter) {
    my ($p1, $p2) = split(/$;/o, $k);
    my $v = $counter{$p1,$p2};

    chomp $p2;

    if($v > 0){
      printf "%s_down_%s %s %d\n", $prefix, $p1, $p2, $v;
      printf "%s_up_%s %s %d\n", $prefix, $p2, $p1, $v;
    }
}

Reading the code, I would say both methods get the same result, but, after executing it, method B, got the right results and method A printed just two elements (4 lines).

After running the method A several times, I can see that the elements printed change each execution.

Anybody knows what is going on here?

Upvotes: 1

Views: 79

Answers (1)

zdim
zdim

Reputation: 66883

The use of foreach with each is wrong

perl -wE'%h=(a=>1,b=>2,c=>3); for (($k, $v) = each %h) { say "$k => $v" }'

prints

c => 3
c => 3

With while instead of foreach we get the correct behaviour.

That this is wrong can be seen from each (my emphasis)

When called on a hash in list context, returns a 2-element list consisting of the key and value for the next element of a hash.

So each iterates and since foreach first generates the whole list (to iterate over) each is made to iterate internally in a (clearly) ill-defined way; this may exhibit an undefined behavior. We end up with an incorrect list of key-value pair(s) for foreach to iterate over (formed internally using each).

Read each page carefully for further subtleties in use of keys, values, and each.


A more telling example

perl -wE'@h{("a".."f")} = (1..6); for (($k, $v) = each %h) { say "$k => $v" }'

prints on my system

e => 5
e => 5

When run correctly with while the e => 5 pair is returned first. So we get that pair, twice.

(Still trying to figure out why twice ...)

Upvotes: 2

Related Questions