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

I do need to do few nested iterations through hash and they are not working properly (thertically i do understand why).
my $h = {'a'=>1, 'b'=>2, 'c'=>3, 'd'=>4,}; while( my ($k1, $v1) = each(%$h)) { say "external $k1 => $v1"; while( my ($k2, $v2) = each(%$h)) { say "internal $k2 => $v2"; } }
But what is the best practical solution for this task? 1. use dclone to have two hash copies - good when they are not huge 2. use array with proper indexing instead of hash

Replies are listed 'Best First'.
Re: Nested iterations throgh hash
by haukex (Chancellor) on Feb 11, 2020 at 20:35 UTC

    I agree with AnomalousMonk that it'd be best if you could explain further what you want to accomplish, with expected output etc. But maybe this is a start:

    use warnings; use strict; use feature 'say'; my $h = {a=>1, b=>2, c=>3, d=>4}; for my $k1 (keys %$h) { my $v1 = $h->{$k1}; say "external $k1 => $v1"; while ( my ($k2, $v2) = each %$h ) { say " internal $k2 => $v2"; } }

      Yes! This is a solution to my problem

      I've checked a few nested "for my $k (keys %$h) {}" loops and they work perfectly. This means that "each" mechanism is not very good to use.

      What is the real task? I have external data supplies as a hash reference, and I do have this hash reference available through all my code, fetching data as required.

        This means that "each" mechanism is not very good to use.

        It depends: As described in each, every hash has an iterator that is per-hash. The code you showed in the root node doesn't work because both of the each calls access the same iterator. My solution fetches the list of keys once at the beginning of the for loop, which will use a bit more memory; I could have done this for both loops, but in this example I knew that the inner each would work because there's nothing else interfering with the iterator, so I kept it. keys does have the advantage that if you want to loop through the hash in a predictable order, you can just say sort keys %hash.

        Also, note that if you want to create the Cartesian product of two sets of hash keys, there are plenty of modules that will do this, see the links in the section "See Also" in my module Algorithm::Odometer::Tiny.

        use warnings; use strict; use Algorithm::Odometer::Tiny; my $h = {a=>1, b=>2, c=>3, d=>4}; my $odo = Algorithm::Odometer::Tiny ->new( [sort keys %$h], [sort keys %$h] ); while ( my @x = $odo->() ) { print "outer: $x[0], inner: $x[1]\n"; }
Re: Nested iterations throgh hash (updated)
by AnomalousMonk (Bishop) on Feb 11, 2020 at 20:25 UTC
    But what is the best practical solution for this task?

    But what is the task? Is it to access the "next" key/value element of the hash in the inner loop? If so, then an array structure would seem more appropriate since a hash has no inherent order beyond key-value pairing (but there are tie-ed, ordered hashes). (Update: As to copying, if you make a copy of the hash by value, you will just end up with two independent each iterators for each copy. If you make a copy of the reference, you're still just operating on the same hash referent!)

    What output do you want from your code?

    Give a man a fish:  <%-{-{-{-<

      In this example, I've try to make the problem very simple, without overwhelming practical applications.

      But the main idea is - I need to do some job for each hash pair. And during this job, I do need to calculate some information related to all pairs from this hash for example.

Re: Nested iterations throgh hash
by shadowsong (Pilgrim) on Feb 11, 2020 at 21:40 UTC

    Hi luxs,

    Your problem stems from this:

    The iterator used by each is attached to the hash or array, and is shared between all iteration operations applied to the same hash or array. Thus all uses of each on a single hash or array advance the same iterator location. All uses of each are also subject to having the iterator reset by any use of keys or values on the same hash or array, or by the hash (but not array) being referenced in list context. This makes each-based loops quite fragile


    To demonstrate, if you use two different hashes - you'll see what I mean. Here's a revised version of your code to demonstrate

    #!/usr/bin/perl use strict; use warnings FATAL => 'all'; use v5.10; # activate say feature my $h1 = {'a'=>1, 'b'=>2, 'c'=>3, 'd'=>4,}; my $h2 = {'v'=>6, 'x'=>7, 'y'=>8, 'z'=>9,}; while( my ($k1, $v1) = each(%$h1)) { say "external $k1 => $v1"; while( my ($k2, $v2) = each(%$h2)) { say "internal $k2 => $v2"; } # getchar to pause iteration after inner loop print "getchar to pause iteration after inner loop\n"; my $c = <>; }

    Best of luck