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

Dear fellow monks,
I have a code below that takes a HoA as input, then processing the combination of the array elements as shown in the result below.

My problem is that the hash will come in varying sizes from time to time. My code below although already gave a correct results, it is awfully rigid. How can I make it more flexible?

Is there any modules that can come to help to create this kind of dynamic nested loops?
use strict; use Data::Dumper; # The HoA below may come in varying # sizes: from "key1" to "key 50" my $hash = { 'key1' => [ 1, 2, 3, 4 ], 'key2' => [ 10, 20, 30 ], 'key3' => [ 100, 200, 300 ], }; # My code below is "hard-coded" # How can I change it to accomodate # dinamycally changing hash size above? my @val1 = @{$hash->{'key1'}}; my @val2 = @{$hash->{'key2'}}; my @val3 = @{$hash->{'key3'}}; foreach my $val1 (@val1) { foreach my $val2 (@val2) { foreach my $val3 (@val3) { print "$val1 - $val2 - $val3\n"; } } }
Note that the example of input sample ($hash) is simplified. In my production code it actually comes in form of HoAoA.
Here is the results of my code above:
1 - 10 - 100 1 - 10 - 200 1 - 10 - 300 1 - 20 - 100 1 - 20 - 200 1 - 20 - 300 1 - 30 - 100 1 - 30 - 200 1 - 30 - 300 2 - 10 - 100 2 - 10 - 200 2 - 10 - 300 2 - 20 - 100 2 - 20 - 200 2 - 20 - 300 2 - 30 - 100 2 - 30 - 200 2 - 30 - 300 3 - 10 - 100 3 - 10 - 200 3 - 10 - 300 3 - 20 - 100 3 - 20 - 200 3 - 20 - 300 3 - 30 - 100 3 - 30 - 200 3 - 30 - 300 4 - 10 - 100 4 - 10 - 200 4 - 10 - 300 4 - 20 - 100 4 - 20 - 200 4 - 20 - 300 4 - 30 - 100 4 - 30 - 200 4 - 30 - 300

Regards,
Edward

Replies are listed 'Best First'.
Re: Creating a Dynamic Nested Loops for HoA
by Zaxo (Archbishop) on Dec 06, 2005 at 11:04 UTC

    You'd have better luck generalizing that if the data was in an AoA. The use of numbering for keys is a clue that this is true. Your desired output is dependent on the ordering of the keys, which a hash does not provide naturally.

    Writing the data as an AoA is just as simple:

    #!/usr/bin/perl my $data = [ [ 1, 2, 3, 4 ], [ 10, 20, 30 ], [ 100, 200, 300 ], ];
    There is a fine trick for producing combinations like you want. It uses glob. The string "{a,b}-{c,d}", fed to glob, will generate the same list as qw/a-c a-d b-c b-d/. Here's how to generate your strings with that:
    my $gpat = do { local $" = '}\ -\ {'; qq({@{[ map { local $" = ','; "@$_"; } @$data ]}}); }; print $gpat, $/; { local $, = "\n"; print glob $gpat; print $/; }
    The glob pattern $gpat is '{1,2,3,4}\ -\ {10,20,30}\ -\ {100,200,300}'. Results follow: Besides the quoted array tricks with $", note that whitespace in the glob expression needs to be escaped as if the shell were acting on it.

    The do block I use for generating the glob pattern looks forbidding, but is not so bad if you break it down from the inside out. It's just two nested cases of setting $" to control what joins quoted arrays, and then supplying a quoted array to each. The nastiest bits are the "extra" curlies which are really quoted text. That construction could be done in several steps with temporary variables and join, if maintainance is an issue.

    After Compline,
    Zaxo

      If you are tied to having the starting HoA, you can get to the AoA suggestions by:
      my $aoa; @$aoa = map {$hash->{$_}} sort keys %$hash;
      Assumes the sort of the keys is the order you want.
Re: Creating a Dynamic Nested Loops for HoA
by BUU (Prior) on Dec 06, 2005 at 10:30 UTC
Re: Creating a Dynamic Nested Loops for HoA
by bageler (Hermit) on Dec 06, 2005 at 17:46 UTC
    This looks like you just want all combinations of the arrays. You can join all your arrays using recursion and then use Math::Combinatorics to get the desired output from your example.


    here's a quick script I wrote to get the output from your example.
    use Math::Combinatorics; my $HoA = { 'key1' => [ 1, 2, 3, 4 ], 'key2' => [ 10, 20, 30 ], 'key3' => [ 100, 200, 300 ], }; sub joinall { my $this = shift; my @ret; if (ref $this eq 'HASH') { for (keys %$this) { push @ret, joinall($this->{$_}); } }elsif (ref $this eq 'ARRAY') { for (@$this) { push @ret, joinall($_); } } else { push @ret, $this; } @ret; } @a = joinall($HoA); my $c = Math::Combinatorics->new(count => 3, data => [sort{$a <=> $b} +@a]); while (my @c = $c->next_combination) { next if length $c[0] == length $c[1] || length $c[1] == length $c[ +2] || length $c[0] == length $c[2]; print join(' - ',@c)."\n"; }
Re: Creating a Dynamic Nested Loops for HoA
by injunjoel (Priest) on Dec 06, 2005 at 23:29 UTC
    Greetings,
    Nothing novel but this question looked like fun so here are my 2 cents. I will leave the Benchmark tests to those more versed in such things :}
    #!/usr/bin/perl -w use strict; use Data::Dumper; # The HoA below may come in varying # sizes: from "key1" to "key 50" my $hash = { 'key1' => [ 1, 2, 3, 4 ], 'key2' => [ 10, 20, 30 ], 'key3' => [ 100, 200, 300 ], 'key4' => [ 'a','b','c','dood'] }; my (@b, @base); #compute combos for(sort keys %{$hash}){ concat_arrays(\@b, $hash->{$_}); } #filter computations @base = grep{ my $count = () = $_ =~ m/\-/g; ($count == (scalar(keys %{$hash})-1)); }sort @base; #double check print $_."\n" for(@base); sub concat_arrays { my ($baseref, $newref) = @_; foreach my $new (@{$newref}){ if(scalar(@{$baseref})){ foreach my $base (@{$baseref}){ push @base, $base." - ".$new; } }else{ push @base, $new; } } @b = @base; }
    The output

    -InjunJoel
    "I do not feel obliged to believe that the same God who endowed us with sense, reason and intellect has intended us to forego their use." -Galileo