neversaint has asked for the wisdom of the Perl Monks concerning the following question:
Dear Masters,
Is there a quick way to transform a HoA into AoH like this,
from:
my $HoA = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
};
Into:
# Desired result:
my $AoH = [
{
flinstones => "fred",
jetsons => "george"
},
{
flinstones => "fred",
jetsons => "jane"
},
{
flinstones => "barney",
jetsons => "george"
},
{
flinstones => "barney",
jetsons => "jane"
},
];
The size of the hash and array in HoA input maybe varying.
Update: Correction made on the correct data structure of the desired results, thanks to pg comments.
Update 2: The actual HoA in my production code looks like this:
my $HoA = {
'1,2,flintstones' => [ "fred-1 foo-2", "barney-1 bar-2" ],
'2,3,jetsons' => [ "george-1 foo-2", "jane-1 bar-2"],
};
Update 3:Additional examples:
With array of only one element
my $HoA3 = {
'flintstones' => [ "fred" ],
'jetsons' => [ "george"],
};
my $ans3= [
{
'flintstones' => 'fred',
'jetsons' => 'george'
}
];
With array of differing size:
my $HoA4 = {
'flintstones' => [ "fred" ],
'jetsons' => [ "george","jane"],
};
my $ans4 = [
{
'flintstones' => 'fred',
'jetsons' => 'george'
},
{
'flintstones' => 'fred',
'jetsons' => 'jane'
}
];
---
neversaint and everlastingly indebted.......
Re: Converting HoA into AoH
by BrowserUk (Patriarch) on Oct 31, 2005 at 04:13 UTC
|
I think this does it? And should handle your real data without problems.
Updated: see comment for the one-line change.
#! perl -slw
use strict;
use Data::Dumper;
sub combs {
my @inputs = @_;
my @saved = my @index = map{ scalar $#$_ } @inputs;
my $next = $#index;
return sub{
if( $index[ 0 ] == -1 ) {
@index = @saved;
return ();
}
my $rv = [ map{ $inputs[ $_ ][ $index[ $_ ] ] } 0 .. $#inputs
+];
if( $index[ $next ] ) {
$index[ $next ]--;
}
else {
$next-- until $index[ $next ] or $next == 0;
$index[ $next++ ]--;
@index[ $next .. $#index ] = @saved[ $next .. $#index ];
$next = $#index;
}
return $rv;
}
}
sub Cnr{
my $n = shift;
return [] unless $n--;
map{
my $x = $_;
map{ [ $_[$x], @$_ ] } Cnr( $n, @_[ ($x + 1) .. $#_ ] );
} 0 .. ($#_ - $n);
}
my $HoA = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
domo => [ "harry", "gato" ],
};
my $AoH = [
map {
my @array;
my $iter = combs @{ $HoA }{ @$_ };
while( my $vals = $iter->() ) {
my %hash;
@hash{ @$_ } = @$vals;
push @array, \%hash;
}
@array;
} Cnr scalar keys %{ $HoA }, keys %{ $HoA }
## } Cnr 2, keys %{ $HoA } ##Replaced
];
print Dumper $AoH;
__END__
P:\test>504165
$VAR1 = [
{
'flintstones' => 'barney',
'jetsons' => 'jane',
'domo' => 'gato'
},
{
'flintstones' => 'barney',
'jetsons' => 'jane',
'domo' => 'harry'
},
{
'flintstones' => 'fred',
'jetsons' => 'jane',
'domo' => 'gato'
},
{
'flintstones' => 'fred',
'jetsons' => 'jane',
'domo' => 'harry'
},
{
'flintstones' => 'barney',
'jetsons' => 'george',
'domo' => 'gato'
},
{
'flintstones' => 'barney',
'jetsons' => 'george',
'domo' => 'harry'
},
{
'flintstones' => 'fred',
'jetsons' => 'george',
'domo' => 'gato'
},
{
'flintstones' => 'fred',
'jetsons' => 'george',
'domo' => 'harry'
}
];
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
|
| [reply] [d/l] |
|
There were two possibilities for the extended example, and I chose the wrong one (again).
But it's only a one line change, to pass the number of keys in the hash rather than the constant 2-- assuming I understood this time?
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
Re: Converting HoA into AoH
by pg (Canon) on Oct 31, 2005 at 01:25 UTC
|
Hopefully you understand what that data structure means. It means that, at the top level, you are creating a hash ref with two pairs of elements, each with a hash ref as a key. My guess is that you are probably looking for:
[
{
flinstones => "fred",
jetsons => "george"
},
{
flinstones => "fred",
jetsons => "jane"
},
{
flinstones => "barney",
jetsons => "george"
},
{
flinstones => "barney",
jetsons => "jane"
},
];
With your data strcture, try this code:
use Data::Dumper;
use strict;
use warnings;
my $blah =
{
{
flinstones => "fred",
jetsons => "george"
},
{
flinstones => "fred",
jetsons => "jane"
},
{
flinstones => "barney",
jetsons => "george"
},
{
flinstones => "barney",
jetsons => "jane"
},
};
print Dumper($blah);
Which prints:
$VAR1 = {
'HASH(0x224ef8)' => {
'flinstones' => 'fred',
'jetsons' => 'jane'
},
'HASH(0x1876560)' => {
'flinstones' => 'barney',
'jetsons' => 'jane'
}
};
| [reply] [d/l] [select] |
Re: Converting HoA into HoH
by Zaxo (Archbishop) on Oct 31, 2005 at 01:28 UTC
|
Your desired output looks more like an AoH. It doesn't have any keys in the top level.
Yet another combinatorial problem. Let's try glob again,
sub hoa2aoh {
my $hoa = shift;
my @keys = keys %$hoa;
my @patterns = map {
local $" = ',';
"{@$_}";
} values %$hoa;
[
map {
my %hash;
@hash{@keys} = split ','; # char changed from "\0"
\%hash;
} glob join ',', @patterns
];
}
As I say, that returns a reference to an array of hashes.
Update: glob wasn't liking the "\0" seperator I used at first. It didn't like whitespace, either. Changed to comma, it works now. Also removed superfluous variable.
| [reply] [d/l] |
|
Dear Zaxo,
Thanks also for your response. Two points here.
Why your subroutine above takes 2 arguments as input?
Another point is, I tried your code,
my $ref = hoa2aoh($HoA);
print Dumper $ref;
it returns:
$VAR1 = [
{
'flintstones' => undef,
'jetsons' => 'george'
},
{
'flintstones' => undef,
'jetsons' => 'jane'
}
];
Please correct me if I misinterpret how your function works.
---
neversaint and everlastingly indebted.......
| [reply] [d/l] [select] |
|
I had an error in the argument to glob, repaired as noted above.
The superfluous variable was $hoh in the line
my ($hoa, $hoh) = shift;
That doesn't really involve two arguments - the shift only brings in one value, and $hoh remained undefined and unused. I only thought I'd want it at first, and piggybacked its declaration on the arg line.
| [reply] [d/l] |
|
|
Re: Converting HoA into AoH
by pg (Canon) on Oct 31, 2005 at 01:57 UTC
|
use Data::Dumper;
use strict;
use warnings;
my $HoA = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
};
my $AoH;
for my $flintstone (@{$HoA->{"flintstones"}}) {
for my $jetson (@{$HoA->{"jetsons"}}) {
push @$AoH, {"flintstones" => $ flintstone, "jetsons" => $jets
+on};
}
}
print Dumper($AoH);
| [reply] [d/l] |
|
I am surprised no-one else complained yet, but this addresses only the example using hard coding, which cannot be useful to the OP given that the number of nesting levels of your loops translates to the number of families. It should be obvious that a solution should be generic to handle a variable number of set-of-sets of unpredisclosed group names each containing an unpredisclosed number of members each with unpredisclosed names.
Update and I should have added that, Algorithm::Loops, which someone has already mentioned, handles precisely the situation where loop nesting level is variable, although I am personally more inclined towards the explicit recursion solutions that were also offered in response.
| [reply] |
|
I am surprised no-one else complained yet
It's out of courtesy, that nobody complained...
| [reply] |
Re: Converting HoA into AoH
by Roy Johnson (Monsignor) on Oct 31, 2005 at 13:01 UTC
|
You might want to look at Algorithm::Loops to generalize the nested-loops solutions that others have offered. Here's a solution in the classical recursive vein.
use warnings;
use strict;
my $HoA = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
};
use Data::Dumper;
print Dumper
to_AoH(%$HoA);
sub to_AoH {
# For no data, return an empty hashref in an arrayref
@_ or return [{}];
# Otherwise, recursively map the first key's values over
# the AoH of the rest of the list
my ($k, $v_aref, @rest) = @_;
[
map {
my $href = $_;
map {
{%$href, $k => $_}
} @$v_aref
} @{to_AoH(@rest)}
]
}
Updated: simpler base case means simpler code.
Caution: Contents may have been coded under pressure.
| [reply] [d/l] |
Re: Converting HoA into AoH
by tphyahoo (Vicar) on Oct 31, 2005 at 09:37 UTC
|
Okay, here we go again, except this time with the cartesian product (minus show duplicates), obtained with Math::Combinatorics::combine. Also, we had to set Data::Dumper::Deepcopy=1 to get a sane output from Data::Dumper.
use strict;
use warnings;
use Data::Dumper;
use Math::Combinatorics qw(combine);
my $HoA_show_characters = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
};
# create all possible combis along the lines of:
#[
# { "flinstones" => "fred" },
# { "flintsones" => "barney" }
#]... etc
my $AoH_show_characters = [];
foreach my $show (keys %$HoA_show_characters) {
foreach my $character ( @{ $HoA_show_characters->{$show} } ) {
push @$AoH_show_characters, ( { $show => $character } );
}
}
#now make all possible combis of 2 from the above aoH.
# but only accept combis from different shows
my $AoH_show_characters_two_at_a_time;
foreach my $a_of_hash_combis( combine( 2, @$AoH_show_characters ) ) {
my $key1 = ( keys %{ $a_of_hash_combis->[0] } )[0];
my $key2 = ( keys %{ $a_of_hash_combis->[1] } )[0];
unless ( $key1 eq $key2) { #key 1 equals key 2
push @$AoH_show_characters_two_at_a_time, ( $a_of_hash_combis
+);
}
}
$Data::Dumper::Deepcopy = 1;
print Dumper($AoH_show_characters_two_at_a_time);
| [reply] [d/l] |
|
| [reply] [d/l] |
|
I'm not really clear on what you mean by that. Is what you had in readmore tags the result that you want? This skips over the women, so I'm assuming that's not it.
Is it the code you are calling complex, or the resulting datastructure? What exactly is the final datastructure you are looking for?
Do you want to take your show / char hash combos two at a time, or is one at a time okay? (In which case you get an array of eight hashes.) If an array of eight hashes is what you want, try dumping $AoH_show_characters after the second chunk of code.
Otherwise, if you want four arrays of two hashes each, like my code produces, I can't see any way to get the job done without this kind of maybe slightly confusing nesting.
Good luck!
| [reply] [d/l] |
Re: Converting HoA into AoH
by tphyahoo (Vicar) on Oct 31, 2005 at 08:40 UTC
|
use strict;
use warnings;
use Data::Dumper;
my $AoH_show_characters = [];
my $HoA_show_characters = {
flintstones => [ "fred", "barney" ],
jetsons => [ "george", "jane"],
};
foreach my $show (keys %$HoA_show_characters) {
foreach my $character ( @{ $HoA_show_characters->{$show} } ) {
push @$AoH_show_characters, ( { $show => $character } );
}
}
print Dumper($AoH_show_characters);
| [reply] [d/l] |
Re: Converting HoA into AoH
by robin (Chaplain) on Oct 31, 2005 at 13:10 UTC
|
sub hoa2aoh {
my ($h, @r) = shift; keys %$h;
my ($k,$v) = each %$h or return [{}];
delete $h->{$k}, return hoa2aoh($h) if !@$v;
my $hd = shift @$v; my $r = hoa2aoh($h);
(exists $_->{$k} ? push(@r, {%$_, $k=>$hd}) : ($_->{$k} = $hd))
foreach @$r;
push @$r, @r; return $r;
}
It destroys the input, so you might need to make a deep copy of it first.
I would explain how it works, but it’s quite a fun puzzle like this. :-)
Update: Roy Johnson (above) has just posted a nicer implementation of the same idea. | [reply] [d/l] |
Re: Converting HoA into HoH
by BrowserUk (Patriarch) on Oct 31, 2005 at 01:19 UTC
|
my $HoH = { map{ $_ => { @{ $HoA->{$_} } } } keys %{ $HoA } };
Update: I completely misread that. Here's the correct version.
my $HoH = { map{ $_ => { map{ $_ => } @{ $HoA->{$_} } } } keys %{ $Ho
+A } };
Update2: And still it was wrong. I think you have your answer now, so I'll shut up.
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
|
Dear BrowserUk,
Thanks a lot for the response. But your code above
returns this only:
$VAR1 = {
'flintstones' => {
'fred' => 'barney'
},
'jetsons' => {
'george' => 'jane'
}
};
Please correct me if I'm wrong.
And also the size of the array may not be even.
---
neversaint and everlastingly indebted.......
| [reply] [d/l] |
Re: Converting HoA into AoH
by Zaxo (Archbishop) on Oct 31, 2005 at 12:05 UTC
|
This is my previous function rewritten to handle spaces in the data by escaping them. Other surprises may be handled similarly.
#!/usr/bin/perl
use Data::Dumper;
sub hoa2aoh {
my $hoa = shift;
my @keys = keys %$hoa;
my @patterns = map {
local $" = ',';
"{@{[map {s/ /\\ /g; $_} @$_]}}";
} values %$hoa;
[
map {
my %hash;
@hash{@keys} = split ',';
\%hash;
} glob join ',', @patterns
];
}
my $HoA = {
'1,2,flintstones' => [ 'fred-1 foo-2', 'barney-1 bar-2' ],
'2,3,jetsons' => [ 'george-1 foo-2', 'jane-1 bar-2'],
};
print Dumper hoa2aoh($HoA);
__END__
$VAR1 = [
{
'1,2,flintstones' => 'fred-1 foo-2',
'2,3,jetsons' => 'george-1 foo-2'
},
{
'1,2,flintstones' => 'barney-1 bar-2',
'2,3,jetsons' => 'george-1 foo-2'
},
{
'1,2,flintstones' => 'fred-1 foo-2',
'2,3,jetsons' => 'jane-1 bar-2'
},
{
'1,2,flintstones' => 'barney-1 bar-2',
'2,3,jetsons' => 'jane-1 bar-2'
}
];
| [reply] [d/l] |
|
Using glob is a delightful trick, but in general you really need to escape the string much more than this. (To see what I mean, try putting a * in one of the strings.)
Instead of s/ /\\ /g, use s/(\W)/\\\\\\$1/g. I know it’s a lot of backslashes, but you really do need that many here!
| [reply] |
Re: Converting HoA into AoH
by Moron (Curate) on Oct 31, 2005 at 09:38 UTC
|
To get all the combinations of the data being asked for (or 'Cartesian Product' as one response put it), there is always Math::Combinatorics
| [reply] |
|
|