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

Hi Monks...

I came across a piece of code, which was provided to demonstrate a limitation when using the each() function:

#!/usr/bin/perl %couples = ( george => gracie, abbot => costello, johnson => boswell ); foreach $c (each %couples) { print "$c\n"; };

The captioned text accompanying this code reads:

"It's important to rember with each() that it will give you only the next key/value pair from the named hash. The output should contain two lines of text, one for george, one for gracie."

In fact, this code produces for me, on my unix system, the following output:

abbot costello

My first question is whether the information in the text regarding the expected code output is erroneous or is it, perhaps, a cross-platform issue, since this was originally DOS code?

My other question is, what is the minimum code that would, indeed print out the names of all three couples and overcome the supposed limitation of the each() function?

Thanks!

Replies are listed 'Best First'.
Re: code that fails to act as expected...
by varian (Chaplain) on Jun 13, 2007 at 11:47 UTC
    When called in list context (like here) each will indeed give the next key/value pair. However one cannot tell for sure that the first key returned is abbot (and its value costello) because hashes are *not* sorted and the key sequence is done (more or less) randomly.

    BTW when you call each in scalar context it would only return the key, not the related value.

    The keys function will help you sort the other question.

Re: code that fails to act as expected...
by citromatik (Curate) on Jun 13, 2007 at 11:48 UTC
    foreach $c (each %couples) { print "$c\n"; };

    each %couple returns, in list context, the next key/value pair. In this case (bacause the %hash is stored in "semi-random" order), returns the pair (abbot,costello). You can visualize this as:

    foreach $c ("abbot","costello") { print "$c\n"; };

    The foreach prints then "abbot" and "costello".

    The expected output can be obtained in list context, for example:

    while (my ($c,$d) = each %couples){ print "$c $d\n"; }

    The shorter way I could imagine to do this is:

    print "$_ $couples{$_}$/" for (keys %couples);

    Hope this helps!

    citromatik

      Thank you, citromatik. When I implement some of your examples, I notice, now, another baffling action:
      my %couples = ( george => 'gracie', abbot => 'costello', johnson => 'boswell',); while (my($c, $d) = each %couples) { print "$c $d\n"; } print "\n"; foreach my $c (each %couples) { print "$c\n"; } print "\n"; while ( my($c, $d) = each %couples) { print "$d $c\n"; }
      The first foreach example code you offered, when run prior to the first while loop you suggested, causes the while loop to drop one of the key, value pairs.The same while loop, run before the foreach loop, outputs all three key, value pairs.

      What causes that behavior? Does the scope of the foreach loop manage to extend, in some way, to the following while loop?

      Thanks, again, for helping with this.

Re: code that fails to act as expected...
by derby (Abbot) on Jun 13, 2007 at 11:59 UTC

    "perldoc -f each" gives you the explanation:

    When called in list context, returns a 2-element list consisting of the key and value for the next element of a hash, so that you can iterate over it. When called in scalar context, returns only the key for the next element in the hash.

    (hmmm ... http://perldoc.perl.org appears to still be down)

    For example:

    #!/usr/local/bin/perl use strict; use warnings; my %couples = ( george => 'gracie', abbot => 'costello', johnson => 'boswell', ); my( $key, $val ); # scalar context - return keys one at a time $key = each %couples; print "$key : $couples{$key}\n"; $key = each %couples; print "$key : $couples{$key}\n"; $key = each %couples; print "$key : $couples{$key}\n"; # reset each's internal iterator $key = each %couples; # list context while ( ($key, $val) = each %couples ) { print "$key - $val\n"; }

    -derby
Re: code that fails to act as expected...
by Moron (Curate) on Jun 13, 2007 at 12:17 UTC
    The hidden trick is that "each" behaves as if it remembers how many times you called it, or more specifically, does something like:

    First time call: build an array of keys in even positions and their values in odd positions and then...

    All calls: shift and return the first two values in stored array.

    The single-operand for loop you show calls "each" only once irrespective of what is returned and so it builds an iteration list of only one key/value pair. The reason a while loop DWIMs on the other hand is that it keeps calling "each", picking up pairs until the array it "kept to itself" is exhausted.

    __________________________________________________________________________________

    ^M Free your mind!