Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Iterating over verbatim hash reference

by rovf (Priest)
on Jan 21, 2010 at 14:07 UTC ( #818722=perlquestion: print w/replies, xml ) Need Help??

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

The following loop does not terminate:

# endless loop here while(my ($r,$s) = each(%{ {x=>5,y=>8}})) { print($r); };
The idea here is to denote the hash verbatim, without introducing a new variable, but this doesn't work. I believe the reason is that on each the hash constructed anew, and $r never advances to the next element. Of course I could code it like this:
# this works my $h={x=>5,y=>8}; while(my ($r,$s) = each(%{ $h })) { print($r); };
(or don't use references at all), but my question is: Is it possible to code this loop without naming the hash?

-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: Iterating over verbatim hash reference
by ikegami (Pope) on Jan 21, 2010 at 19:16 UTC

    Here are four very simple/clean syntaxes to choose from:

    • { my %h = ( x=>5, y=>8 ); while ( my ($r,$s) = each %h ) { ... } }
    • for ({ x=>5, y=>8 }) { while ( my ($r,$s) = each %$_ ) { ... } }
    • for (paired( x=>5, y=>8 )) { my ($r,$s) = @$_; ... }
      where paired is defined as
      sub paired { my @pairs; push @pairs, [ shift, shift ] while @_; return @pairs; }
    • paired { my ($r,$s) = @_; ... } ( x=>5, y=>8 );
      where paired is defined as
      sub paired(&@) { my $cb = shift; my @rv; push @rv, $cb->(shift, shift) while @_; return @rv; }
Re: Iterating over verbatim hash reference
by rubasov (Friar) on Jan 21, 2010 at 17:16 UTC
    I think this is possible, however my solution is cryptic and ugly, but it works. I recommend you to copy-paste the following snippets to a syntax-highlighting editor, otherwise you will get lost. Let's start with an easier version that still uses a variable for holding a coderef to an anonymous sub, later on we will eliminate it.
    #! /usr/bin/perl use strict; use warnings; my $f = sub { return @_ == 1 ? undef : ( [ splice @_, -2 ], @_ == 1 ? () : &{ $_[0] }(@_) ); }; map { print "$_->[0] -> $_->[1]\n" } $f->( $f, %{ { x => 5, y => 8, z => 42 } } );
    A little explanation: I'm defining a recursive anonymous sub, that gets its coderef as its first argument, and the hash listified as the following arguments. Then returns undef for the recursion base case (@_==1), or returns an arrayref for its last two args, and calls itself for the remaining args (if there are enough remaining args). Okay, try it, the result is this:
    z -> 42 x -> 5 y -> 8
    Then eliminate the var for the coderef: replace the occurances of $f in the map statement with the former sub{} definition and we get this:
    #! /usr/bin/perl use strict; use warnings; map { print "$_->[0] -> $_->[1]\n" } &{ sub { return @_ == 1 ? undef : ( [ splice @_, -2 ], @_ == 1 ? () : &{ $_[0] }(@_) ); } }( sub { return @_ == 1 ? undef : ( [ splice @_, -2 ], @_ == 1 ? () : &{ $_[0] }(@_) ); }, %{ { x => 5, y => 8, z => 42 } } );
    I'm sure you don't want to use this in real code, however this shows the idea. And sorry but I cannot see any easier solution at this moment.
      I'm sure you don't want to use this in real code
      Except, maybe, on a day I am very angry to my co-workers :-P I really appreciate your elaborate idea (wonderful analysis!), but it bites us from behind: By the replication step in your transformation, you introduce two anonymous subs which are exactly identical, so being good programmers, we will of course give a name to the sub and end up with only one copy of the subroutine; but as soon we do this, we have just traded the name of a hash for a name of a sub (and indeed, this can be done with any other data element as well), so it's not a real gain...

      -- 
      Ronald Fischer <ynnor@mm.st>

        That duplication trick is really not nice, but I don't see a way how can this be avoided in perl5 (probably a wiser monk may shed some light on this).

        Some other languages have nicer constructs for recursive anonymous functions without this trickery, look at a few examples at stackoverflow.com.

        However I treated the challange of "eliminating named variables", more as a (math-like) challange of using functional programming concepts in perl5, rather than "how to achieve tha same results in real code".

        update:
        we have just traded the name of a hash for a name of a sub [...], so it's not a real gain
        That's true for the particular code above, however think of this issue more generally. If we insist on retaining the sub identifier, that still has its advantages, because in lots of code, we traded lots of hash identifiers for one sub identifier.
Re: Iterating over verbatim hash reference
by chromatic (Archbishop) on Jan 21, 2010 at 19:58 UTC
    I believe the reason is that on each the hash constructed anew....

    That's due to while, not each. If you used for, I'd find the code confusing (the while/each idiom is common), and you'd need a comment to explain why. It's simpler to use the temporary variable here.

Re: Iterating over verbatim hash reference
by BrowserUk (Pope) on Jan 22, 2010 at 00:24 UTC

    You can't avoid a name, but (with 5.10) you can limit it's scope:

    while( my ($k, $v) = each %{ state $a = { 'a'..'z' } } ) { say "$k :> +$v" };; w :> x e :> f a :> b m :> n s :> t y :> z u :> v c :> d k :> l q :> r g :> h i :> j o :> p

    Not that I can see any justification for doing so.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Iterating over verbatim hash reference
by Herkum (Parson) on Jan 21, 2010 at 14:32 UTC

    A better question would be, why would insist on sticking the information in the while loop conditional check instead of a variable?

      why would insist on sticking the information in the while loop conditional check instead of a variable?
      I have a slight preference for omitting variable names if I don't really need them. But of course, if this would be the only reason, I would say in this case: What the heck, let's name the variable and forget it. But the truth is, once I stumble over such a problem, I get interested in the problem as such, because it indicates that this might lead to some aspect of Perl programming which I have not understood yet - and that's the main reason why I'm looking for a solution.

      -- 
      Ronald Fischer <ynnor@mm.st>
        Variables can help you clarify what you code does. Something an obscure data structure does not. For example,
        my $DEBUG = 1; #... later in your code if ($DEBUG) { warn "Something happened\n" }

        I bet your thinking that this is pretty obvious but I still want to reduce the number of variables.

        If you think you have too many variables in a section of code, chances are you have not abstracted out it out enough. Move more of your code into other subroutines to enhance clarity.

        my $dimensions = _get_dimensions_for(5,8); while (my ($r,$s) = each %{ $dimensions } ) { print($r); } sub _get_dimensions_for { my $x = shift; my $y = shift; return { x=> $x, y=> $y } }

        Note: Thanks to ikegami for pointing out my error.

        I have a slight preference for omitting variable names if I don't really need them.

        If you are concerned about cluttering up a (possibly over-broad) scope with needless nonce variable names (not an entirely unreasonable concern, IMO), why not just enclose the sub-section of code in its own limited sub-scope?

        >perl -wMstrict -le "{ my %h = qw(a 1 b 2 c 3); while (my ($k, $v) = each %h) { print qq{$k -> $v}; } } " c -> 3 a -> 1 b -> 2
Re: Iterating over verbatim hash reference
by AnomalousMonk (Bishop) on Jan 21, 2010 at 18:59 UTC

    There are some things mere mortals are not meant to do.

    while(my ($r,$s) = each(%{ {x=>5,y=>8} })) { print($r); };

    ... is one of them.

    >perl -wMstrict -le "while (my $endlessly = shift @{[ qw(but why?) ]}) { print qq{$endlessly...}; sleep 1; } " but... but... but... Terminating on signal SIGINT(2)

    ... is another.

Re: Iterating over verbatim hash reference
by johngg (Canon) on Jan 21, 2010 at 23:29 UTC
Re: Iterating over verbatim hash reference
by Anonymous Monk on Jan 21, 2010 at 14:15 UTC
    for( my $h = { qw' x 5 y 8 ' }; my($r,$s)=each %$h; ){ print " $r = $s \n"; } __END__ y = 8 x = 5
    You're the first person to call it verbatim hash in 10 years :) SPRINGLE
      This doesn't answer my question, because you are introducing a variable for the hash ref here, i.e. $h. Of course this works, as you see from the 2nd example in my original posting. The point is to do it without naming the hash/hashref.

      -- 
      Ronald Fischer <ynnor@mm.st>
        ... without introducing a new variable ...

        The following variation doesn't introduce a new variable because  $_ already exists! (I believe the following is also Stupid Hash Trick #58.)

        >perl -wMstrict -le "for ({ qw(a 1 b 2 c 3) }) { while (my ($k, $v) = each %$_) { print qq{$k => $v}; } } " c => 3 a => 1 b => 2
        It isn't possible, you need a name.
Re: Iterating over verbatim hash reference
by Ratazong (Monsignor) on Jan 21, 2010 at 14:17 UTC
      I thought about using for, but how do I loop in this way over a hash, so that the key-value association is maintained? Of course I could do something like

      for ([qw(key1 val1)],[qw(key2 val2)],....) { my $key=$_->[0]; my $val=$_->[1]; ... }
      but this too is not quite a response to my original problem, which is, looping over a hash. Or maybe I misunderstood you? Could you give some example?

      -- 
      Ronald Fischer <ynnor@mm.st>
Re: Iterating over verbatim hash reference
by LanX (Cardinal) on Jan 23, 2010 at 01:45 UTC
    Easily extending foreach to multiple iterator-vars is a problem in perl5.

    That's why the for syntax was extended in perl6

    for %hash.kv -> my $key, $val { say "$key corresponds to $val"; }

    Me myself I would love to have a real macro mechanism to solve this properly in perl5, anyway I can suggest you some code to simulate it at least in a functional way

    use strict; use warnings; { my %lists; sub deal { my $aref=shift; my $id= \$_[0]; #print $aref,$id; $lists{$id}=$aref if (! exists $lists{$id} ); if (@{$lists{$id}}){ for(@_) { $_=shift @{$lists{$id}}; } return 1; # could be extended to # * iteration counter # * remaining list-elements # * a list of all of these } else { delete $lists{$id}; return 0; } } } $\="\n"; #--------- here we go while (deal [1..9] => my ($a,$b,$c) ){ print $a,$b,$c; while (deal [a=>1,b=>2] => my ($a,$b) ){ print $a,$b; } } #--------- print "EXIT"; __DATA__ 123 a1 b2 456 a1 b2 789 a1 b2 EXIT

    I'm not sure if it works prior to 5.10, IMHO there was an issue about "late" my declarations.

    Some things to note:

    1. The anonymous array will be build each time, but ignored after the first iteration.
    2. my first guess was to call the function "iter", but "deal" (like to deal cards to players) should make clear that it's not a lazy evaluated iterator-function!
    3. the reference of the first iterator-variable (here \$a) is taken as identifier to avoid confusion when nesting iterations, like with $_ when foreaching you should never mess around with the same instance of $a.
    4. you are flexible to use as many iterator-vars as you want, not only two. Taking one simulates foreach, with the advantage that gotos into the body are possible.
    5. the fat comma => is just syntactic sugar, but I think its more readable like this (and even a little more intuitive than ikegamis suggestions).
    6. the performance problem with rebuilding the list at each pass may be solved by passing a code-block,  sub {LIST} which is only once evaluated internally. When using prototype (&@) even the sub could be omitted².

    Other approaches¹ solving the repeated LIST creation may be to split functionality onto two functions deal and to such that either

     for (deal(LIST);to(VARS)) { ... }

    or

     while ( deal(LIST) .. to(VARS) ) { ... }

    work! (But error-handling becomes trickier)

    In the latter correct return values would make the flip-flop operator (scalar ..) build the list only once!

    Cheers Rolf

    UPDATES:

    ¹) thats really complicated to realize avoiding a nesting mess, because it's not possible to identify from which codeposition to() is called, the linenumber in caller is not sufficent.

    ²) maybe the best solution! The call syntax for iterating over a literal hash would be

    while( deal {a=>1,b=>2} my($k,$v) ) {..}
    Please note, no (fat) comma needed, because now we are passing code returning a list NOT a hashref! That's analogous to map and grep's syntax.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://818722]
Approved by moritz
Front-paged by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (4)
As of 2020-10-21 05:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favourite web site is:












    Results (212 votes). Check out past polls.

    Notices?