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

I try to iterate over a (nested) hash.
In my program I use some recursive function within the first while loop and call the second while loop later. This is a minimal example of what I am doing. The problem is, that this is an infinite loop, but I don't really understand why. I guess it is because of some internal states of perl.
my %test = ('a' => {'b' => 'b'}); while ( my $name, $value ) = each(%test) ) { print $name1; while ( my ( $name2, $value2 ) = each(%test) ) { print $name2; } }
I tried to use clone on the hash to get around the problem, but it did not work. What is the best way to get around this problem (not iterating twice is not an option at the moment)?

I use perl v5.10.0 if that matters.

Update:: Using clone creates an infinite loop even without the second while loop.

Replies are listed 'Best First'.
Re: Nested while each over a hash -> infinite loop
by davido (Cardinal) on Jun 10, 2011 at 08:04 UTC

    From the documentation for each:

    After each has returned all entries from the hash or array, the next call to each returns the empty list in list context and undef in scalar context. The next call following that one restarts iteration.

    The outer while loop grabs the only element in %test. The inner while loop gets an empty list, and consequently never executes. So the outer while loop tests each again. This time it has reset, so it again returns the first (and only) element from %test.

    Nothing wrong with Perl. Your script is flawed.

    Also, if you were using use strict; at the top of your script you would have discovered that print $name1 is not working out so well, since there is no $name1. The outer loop creates $name, not $name1. Furthermore, your code won't even compile as your outer while loop's parens don't nest properly.

    You may want something more like this:

    use strict; use warnings; my %test = ('a' => {'b' => 'b'}); while ( my ( $name, $value ) = each %test ) { print $name; while ( my ( $name2, $value2 ) = each %{ $value } ) { print $name2; } }

    Dave

      Ah, now I get it. Thanks.

      The $name1 thing was a typo. I have to use different computers for scripting and for browsing the web.

      Cloning before the first while loop seems to work. I think I will go with that.

        My original post has been updated with some code to show how to do what you probably intended to do. Have a look. I think it will get you back on track. You may need to browse over one or more of the following: perlreftut, perllol, and perldsc.


        Dave

Re: Nested while each over a hash -> infinite loop (fixed)
by BrowserUk (Patriarch) on Jun 10, 2011 at 08:42 UTC

    If your hash can be of any real size, I suggest taking an array of keys and iterating over it with for loops rather than cloning the hash.

    my @keys = keys %hash; for my $i ( @keys ) { my( $k1, $v1 ) = ( $i, $hash{ $i } ); for my $j ( @keys ) { my( $k2, $v2 ) = ( $j, $hash{ $j } ); ... } }

    It will only require around 1/3 of the extra memory and run in less than half the time.


    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: Nested while each over a hash -> infinite loop
by choroba (Cardinal) on Jun 10, 2011 at 08:09 UTC
    You can only have one iterator per a hash. When you call each in the inner loop, you get undef (end of iteration), next call to each (in the outer loop again) starts again from the beginning. If you want two iterators, make a copy of the hash.