Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

'each' syntax oddity?

by SuperCruncher (Pilgrim)
on Mar 07, 2002 at 13:07 UTC ( [id://150000]=perlquestion: print w/replies, xml ) Need Help??

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

Last night I was experimenting with XML::Parser, trying to parse an XML document. I encountered what I consider to be a pretty strange syntax/context oddity.
my @array = qw(foo bar); while (my ($attrib, $value) = each (@array) ) { print "$attrib=$value\n"; }
produces the error message:
Type of arg 1 to each must be hash (not private array) at test.pl line 2, near "@array) "

Peculiar, I thought--why can't the array be "automagically" converted to a hash? But anyway.... I know I can create a hash by assigning an array to a hash, and although each requires a HASH, I thought any expression that evaluates to a hash would be ok. So I try the following code:

my @array = qw(foo bar); while (my ($attrib, $value) = each (my %hash = @array) ) { print "$attrib=$value\n"; }
I expected that to work: surely the my %hash = @array code is evaluated first, resulting in a hash. But this code didn't work either, I got the error:
Type of arg 1 to each must be hash (not list assignment) at test.pl line 2, near "@array) "

So I decided to insert another line before the while statement, as shown below:

my @array = qw(foo bar); my %hash = @array; while (my ($attrib, $value) = each (%hash) ) { print "$attrib=$value\n"; }
This did finally have the desired effect, but it seems incredibly redundant and un-Perl-like to have to have the array to hash assignment as a separate statement. Obviously if I want to create a hash, perl needs to create a hash table to do the look-ups so hashes aren't identical to arrays, but it seems strange that it can't automatically do this, especially when many Perl operators and functions act in magical ways (e.g. the ++ operator on strings).

Replies are listed 'Best First'.
(crazyinsomniac) Re: 'each' syntax oddity?
by crazyinsomniac (Prior) on Mar 07, 2002 at 13:22 UTC
    see perlref: while (my ($attrib, $value) = each %{{@hash}}) ) { ...

    for a clue, try print ref {qw/1 2 3/}

    for an interesting tidbit run perl -we"sub f{{@_}};print ref(f(1,2,3))"
    and then perl -we"sub f{return {@_}};print ref(f(1,2,3))"

    from perldoc -f each

    ...There is a single iterator for each hash, shared by all "each", "keys", and "values" function calls in the program; it can be reset by reading all the elements from the hash, or by evaluating "keys HASH" or "values HASH". If you add or delete elements of a hash while you're iterating over it, you may get entries skipped or duplicated, so don't. Exception: It is always safe to delete the item most recently returned by "each()"...
    now you probably have a better idea of what a hash is versus what an array is ... most likely the only reason no automagic happens is because it's not come up before, and isn't that particularly useful.

    Also note that the order of the values of @array will not stay in the same order they were in, because this is a hash, but of course pairs will stay pairs ;)

    update jmcnamara says the above creates an infinate loop; well that it does, but you gotta admit, it's an ingenious way to get random pairs ;)

    you're probably better off writing something like

    while(my($one,$two)=(shift(@aray),shift(@aray)) ) { ... #or even while(my($one,$two) = splice(@aray,0,2) ) { ...
    but that will of course modify @array

    update: I thought about getting smart, so I tried

    my @array = qw/ 1 2 3 4/; while(my($one,$two) = Each(\@array) ) { print "$one = $two \n"; } sub Each { my $r = shift; my $counter; unless(@$r) { warn "empty array"; return; } unless($r->[-2] eq $r) { push(@$r, $r); push(@$r,0); } $counter = $r->[-1]; my $one = $counter < scalar(@$r) - 3 ? $r->[$counter] : undef; $counter++; my $two; if( $counter < scalar(@$r) - 2 ) { $two = $r->[$counter]; } else { warn "finally gone through it"; return; } $r->[-1] = $counter; if(2 == @$r) { warn "gone trhough it, nothing left to see"; return; } return ($one, $two); }
    but that WORKS don't work. It's probably got something to do with the fact that undef is an lvalue, and it will still evaluate to true in that while syntaxt. SOmebody's got to know of a way to fake what i'm trying to do
    when I run it, I get
    F:\dev>perl each.pl
    1 = 2
    2 = 3
    3 = 4
    finally gone through it at each.pl line 32.
    
    If you plan on using the array later, you'll probably want to pop off the last 2 values ;)(it's be a nice method for your package, make it private, prepend a _)

     
    ______crazyinsomniac_____________________________
    Of all the things I've lost, I miss my mind the most.
    perl -e "$q=$_;map({chr unpack qq;H*;,$_}split(q;;,q*H*));print;$q/$q;"

Re: 'each' syntax oddity?
by steves (Curate) on Mar 07, 2002 at 14:34 UTC

    One other side effect of the single iterator is that you can't use nested each loops on the same hash. Try this:

    use strict; my %hash = (FOO => 'foo', BAR => 'bar', FUBAR => 'fubar'); my ($key1, $val1); my ($key2, $val2); while (($key1, $val1) = each %hash) { print "OUTER: $key1=$val1\n"; while (($key2, $val2) = each %hash) { print " INNER: $key2=$val2\n"; } }
    Hint: Make sure you're near the break/interrupt key.

    This bit me once where a not-so-good design had me iterating through the same hash nested, but pretty far apart so it wasn't obvious. Imagine that the inner loop above is really several calls away in another package and you get the idea.

Why do Perlers hate C-style for-loops, especially when they're called for?
by dragonchild (Archbishop) on Mar 07, 2002 at 14:39 UTC
    for (my $i = 0; $i < $#array; $i+=2) { my ($first, $second) = @array[$i, $i+1]; # Do stuff here ... }

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Why do some people speak Perl with a C accent? {grin} A more native Perl solution:
      my @copy = @array; while (my ($first, $second) = splice @copy, 0, 2) { # do stuff }
      Your code has many potential "off by one" locations. Yes, it's possible to get it exactly right, but much safer to let Perl deal with the edges than for me to keep computing the edges. Call it defensive programming.

      -- Randal L. Schwartz, Perl hacker

Re: 'each' syntax oddity?
by impossiblerobot (Deacon) on Mar 07, 2002 at 14:42 UTC
    Even if your second example worked, it wouldn't work. :-)

    Since you are re-creating the hash every time through the loop, you would be (eternally) returning the first key-value pair from the array. This should happen with any case where you are creating the hash within the 'while' statement, which makes your third example, IMO, decidedly Perl-ish.

    (And of course the normal caviat about possible duplicate keys when converting an array to a hash still stands.)

    Impossible Robot

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://150000]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (8)
As of 2024-04-23 08:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found