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

Dear Perl Monks,

I'm really puzzled and a bit scared about the following issue.

I want to get all the data out of a hash in a hash.

I used to do this (within a subroutine):
while (($s_pattern,$p_pattern) = each %{$patterns{$gen}}) { # ... }
Without $gen or %{$patterns{$gen}} changing, sometimes, for no apparent reason, this block was skipped when the subroutine was invoked.

However, if I do:
foreach $s_pattern (sort keys(%{$patterns{$gen}})) { $p_pattern = ${$patterns{$gen}}{$s_pattern}; # ... }
the problem disappears, and the loop is never skipped.

Any advice on what's going here? Is there something wrong with using each on a hash in a hash of hashes?

Thanks a lot!

Marco

Replies are listed 'Best First'.
Re: trouble with each and a hash of hashes
by ccn (Vicar) on Sep 11, 2004 at 13:04 UTC

    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.
      If you reset the iterator, shouldn't it start all over again? So instead of skipping the while loop, it would run it again from the very beginning.

      CountZero

      "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: trouble with each and a hash of hashes
by CountZero (Bishop) on Sep 11, 2004 at 13:12 UTC
    It must be something with the data in your hash or HOH.

    The while will fall through when the assignment($s_pattern,$p_pattern) = each %{$patterns{$gen}} is false. This should normally only happen when you reach the "end" of your HOH.

    foreach on the other hand will dutyfully churn through the whole of the array (keys returns an array of key-values) even if they are empty, undef or NULL. Strange as it may seem, but they the key-value of a hash can be empty and the value-value can be undef. Did you have a look at your data-structures with Data::Dumper? Always a great help to do!

    Update: fixed a typo.

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

      Thanks!

      I tried Data::Dumper, and things get weirder.

      If I print the hash with Data:Dumper, the problem disappears.

      So, in the following case sometimes while fails:
      while (($s_pattern,$p_pattern) = each %{$patterns{$gen}}) { # ... }
      But in the following case the while loop is always entered:
      print Dumper(%{$patterns{$gen}}); while (($s_pattern,$p_pattern) = each %{$patterns{$gen}}) { # ... }
      Nothing else changes!

      Mystery...

      Marco
        Are you using each before this in your program? Are you calling the subroutine twice in a row without making changes to the HoH in between? If the while loop is preceded chronologically by another while loop with the same structure that might explain why the second loop is being skipped.

        Data::Dumper probably resets the iterator and so it runs through the HoH normally. What happens if you change "print Dumper" to "keys"? Does it still cause the while loop to run?

        And it is with exactly the same data each and every time?

        Strangeness abounds!

        CountZero

        "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: trouble with each and a hash of hashes
by NetWallah (Canon) on Sep 11, 2004 at 19:08 UTC
    perldoc -f each

    When the hash is entirely read, a null array is returned in list context (which when assigned produces a false (0) value), ...

    This leads me to believe that one of these could cause the problem:

    • You are exiting the "while ..(..each)" loop early, whthout exhausting the hash. The next time you return, it would pick up where it left off for that particular hash (If I understand the docs right).
    • You have another "each" working on the same hash elsewhere in the code, which exits early.

    To "reset" the while (I would never do that in production code, before finding what causes the problem), simply do an throw-away keys() or (values) fetch prior to the "each".

        Earth first! (We'll rob the other planets later)

      Thanks a lot!

      Following the advice given to me by y'all and doing some more debugging I found out the problem:

      Once I find what I want during the "while ..(..each)" loop, I exit the loop.

      The next time, "each" starts from the pair in the list after the one where I stopped the previous time.

      If I exit the loop during the iteration in which "each" returned the very last pair in the list, the next time the loop is not even entered.

      So, I will go for a foreach loop instead.

      Well, thanks again -- now it looks obvious but I would have never been able to find out by myself!

      Marco
        So, I will go for a foreach loop instead.

        As a solution, that will work fine--but it's not without it's downside.

        Using while each, avoids a (possibly significant) overhead that for keys or for values impose: that of constructing a list. If your hash is small, and if your going to iterate most of the elements anyway, then that may be acceptable.

        However, there is a better way: If you call keys on the hash(ref) before entering your while loop, it will reset the iterator and you will avoid the problems you detailed above. keys is specifically optimised for this purpose. From perlfunc:

        As a side effect, calling keys() resets the HASH's internal iterator, see each. (In particular, calling keys() in void context resets the iterator with no other overhead.)

        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon