Reputation: 9025
I am writing a script that involves printing the contents of a hash of a hash of arrays.
ex (pseudo code):
my %hash = ();
$hash{key1}{key2} = ['value1', 'value2', 'value3', . . .];
or
$hash{key1}{key2} = @array_of_values;
Basically I want to be able to do this for any number of key combinations and be able to loop through all of the possible key/value pairs (or probably more correctly stated as key,key/array pairs since each value is actually an array of values and each array has 2 keys associated with it) and print the output in the following format:
"key1, key2, value1, value2, value3, . . .\n"
ex:
#!/usr/bin/perl
use strict;
use warnings;
# initialize hash
my %hash = ();
my $string1 = "string1";
my $string2 = "string2";
# push strings onto arrays stored in hash
push @{$hash{a}{b}}, $string1;
push @{$hash{a}{b}}, $string2;
push @{$hash{c}{d}}, $string2;
push @{$hash{c}{d}}, $string1;
# print elements of hash
# (want to do this as a loop for all possible key/value pairs)
local $, = ',';
print "a, b, ";
print @{$hash{a}{b}};
print "\n";
print "c, d, ";
print @{$hash{c}{d}};
print "\n";
system ('pause');
The output from this code is shown below:
a, b, string1,string2
c, d, string2,string1
Press any key to continue . . .
I was thinking about using the each
operator but it appears to only work for one dimensional hashes. (each
only returns one key value pair, it does not work correctly when there are 2 keys involved)
How can I streamline this code to traverse through the hash in a loop and print the desired output regardless of how big my hash gets?
Upvotes: 2
Views: 2153
Reputation: 6388
Sounds like you need a nested loop ... there's likely a way to do this with map
that is harder to remember and less readable ;-) (speaking only for myself of course - I look forward to seeing a map
solution appear here!):
#!perl -l
my %hash = ();
$hash{key1}{key2} = ['value1', 'value2', 'value3',];
for my $outer (keys %hash) {
for my $inner (keys $hash{$outer}) {
print join(', ', $outer, $inner, @{ $hash{$outer}{$inner} } )
}
}
output:
key1, key2, value1, value2, value3
There's also Data::Printer
, Data::Seek
,Data::Dive
and several other convenience modules if things are really complicated. Some require the use of hash references though which may complicate a simpler solution or make a larger and more difficult problem more easily possible ...
Cheers,
EDIT: moved "nested map" into a separate answer.
Upvotes: 1
Reputation: 6388
I asked for it :-( ... a "nested map" (??).
#!perl -l
my %hash = ();
$hash{key1}{key2} = ['value1', 'value2', 'value3',];
map{ &{ sub{
map{ print for @{$hash{$_[0]}{$_}}} keys $hash{$_} }}($_)} keys %hash;
output:
value1
value2
value3
NB: I think technically (behind the scenes) it's still a loop and no more efficient. It seems that if you "nest" the calls to map
and wrap the inner map{}
with an anonymous sub
(the {& sub { ..
bit) then you can reference the outer map
topic values via @_
(i.e. in $_[0]
as they come in to the inner map
... but only if they are in list context ($_)
... errm (?!?) OK I'm just grasping here.
Anyway, it works but looks messier than it should - possibly because it is an "anti-pattern". One benefit is that it makes "nested for
loops" seem much less messy and easier to read.
Upvotes: 1
Reputation: 9530
To print a hash of hash of arrays, you can iterate through the data structure using foreach
or for my
:
# keys of the outer hash
foreach my $oh (keys %hash) {
# keys of the inner hash
foreach my $ih (keys %{$hash{$oh}}) {
# $hash{$oh}{$ih} is the array, so it can be printed thus:
print join(", ", $oh, $ih, @{$hash{$oh}{$ih}}) . "\n";
# or you can iterate through the items like this:
# foreach my $arr (@{$hash{$oh}{$ih}})
# { doSomethingTo($arr); }
}
}
And for all you cartography fans, here's a map
version:
map { my $oh = $_;
map { say join( ", ", $oh, $_, @{$hash{$oh}{$_}} ) } keys %{$hash{$_}}
} keys %hash;
Upvotes: 2
Reputation: 30851
You haven't stated why you want to print the contents of your HoHoA (hash of hash of arrays). If it's for debugging purposes I'd use either Data::Dumper (core) or Data::Dump (on CPAN).
use Data::Dumper qw(Dumper);
print Dumper \%hash;
or
use Data::Dump qw(pp);
pp \%hash;
Assuming that there's a reason you want the output formatted per your sample (and that it's always and only a HoHoA) I'd use a nested loop:
while (my ($ok, $ov) = each %hash) {
while (my ($ik, $iv) = each %$ov) {
say join ',', @$iv;
}
}
I don't recommend using map
. It's best used for list transformation rather than flow control and it's awkward to keep track of the outer and inner keys with nested map
blocks both using $_
for different things. Because you and G. Cito expressed interest in seeing one, though, here it is:
say foreach map {
my $o = $_;
map {
my $i = $_;
join ',', $o, $i, @{$hash{$o}{$i}}
} keys %{$hash{$o}};
} keys %hash;
Upvotes: 3
Reputation: 126772
This is tidiest with a recursive subroutine, and since it will only recurse a few time it isn't a wasteful solution.
use strict;
use warnings;
my %hash;
my ($string1, $string2) = qw/ string1 string2 /;
push @{$hash{a}{b}}, $string1;
push @{$hash{a}{b}}, $string2;
push @{$hash{c}{d}}, $string2;
push @{$hash{c}{d}}, $string1;
print_data(\%hash);
sub print_data {
my ($ref, $prefix) = (@_, '');
if (ref $ref eq 'ARRAY') {
print $prefix, join(', ', @$ref), "\n";
}
else {
print_data($ref->{$_}, "$prefix$_, ") for sort keys %$ref;
}
}
output
a, b, string1, string2
c, d, string2, string1
Upvotes: 4
Reputation: 67920
Using each
works perfectly fine even for a multilevel hash, you just have to make sure that the argument is a hash. Here is an example of how you can do it. I also showed how I would initialize that hash of yours.
use strict;
use warnings;
use v5.14;
my $string1 = "string1";
my $string2 = "string2";
my %hash = (
a => {
b => [ $string1, $string2 ],
},
c => {
d => [ $string2, $string1 ],
}
);
for my $key (keys %hash) {
while (my ($k, $v) = each %{ $hash{$key} }) {
say join ", ", $key, $k, @$v;
}
}
Output:
c, d, string2, string1
a, b, string1, string2
Note the simplicity of using @$v
to get to your innermost array, rather than the somewhat cumbersome alternative @{ $hash{$key}{$k} }
.
Upvotes: 6
Reputation: 9025
I've been thinking about it and came up with one possible solution. If anyone has a more elegant solution I would love to see it. Thanks!
Here is the solution I came up with:
#!/usr/bin/perl
use strict;
use warnings;
# initialize hash
my %hash = ();
my $string1 = "string1";
my $string2 = "string2";
# push strings onto arrays stored in hash
push @{$hash{a}{b}}, $string1;
push @{$hash{a}{b}}, $string2;
push @{$hash{c}{d}}, $string2;
push @{$hash{c}{d}}, $string1;
# prints hash of hash of arrays in a loop :)
my @keys1 = keys %hash;
for my $key1 (@keys1) {
my @keys2 = keys $hash{$key1};
for my $key2 (@keys2) {
local $" = ', ';
print "$key1, $key2, @{$hash{$key1}{$key2}}";
print "\n";
}
}
system ('pause');
This solution doesn't care about the order of the keys though so it will print in a random order.
Output:
c, d, string2, string1
a, b, string1, string2
Press any key to continue . . .
Upvotes: 1