trying to understand hash of hashes or multidimensional arrays

S

Sherm Pendley

This problem may be something simple that I am failing to grasp ...
I've read dozens of postings about 'hashes of hashes' and
'multidimensional hashes' and 'hashes of arrays', and I still can't
figure out how to use such a structure to do what I want.

Reading "perldoc perllol", and "perldoc perldsc" would be a great start.

sherm--
 
J

jguad98

This problem may be something simple that I am failing to grasp ...
I've read dozens of postings about 'hashes of hashes' and
'multidimensional hashes' and 'hashes of arrays', and I still can't
figure out how to use such a structure to do what I want.

I have a list that I read into an array. Each element is a text string
like this (it's an object identifier):

"region.level.location.system"

There's an unknown number of regions, then for each region there are an
unknown number of levels, in each level an unknown number of locations,
and in each location an unknown number of systems.

The labels or names for each node of the object name may be duplicated
within different 'branches' ... they are unique only within their
context of 'region.level.location.system'. For example, there can be
multiple instances of a 'location' called 'home' as long as each 'home'
is in a different 'region' or 'level'.

So I want to read this text data into my script, store it in a
multidimensional array (or should it be a 'hash of hashes'?). Then I
need to generate a report that shows me the relationships in an orderly
(almost graphic) manner ... I want to be able to print out something
like this (actual formatting is not important, just the organization):

region1

region1.level1
region1.level2

region1.level1.location1
region1.level1.location2

region1.level2.location1
region1.level2.location2

region1.level1.location1.systemA
region1.level1.location1.systemB

region1.level1.location2.systemA
region1.level1.location2.systemB

region1.level2.location1.systemA
region1.level2.location1.systemB


.... so on and so forth ... iterating through every region, every
level, every location, every system

So how can I put the data into a multidimensional array or hash and
then pull that data back out as needed? I've thought of using multiple
arrays and then having elements of arrays referring to other arrays,
but that gives me a headache and I really believe this should be doable
via a single multidimensional hash ... I just can't figure out how.

.....

More on the issue of multidimensional hashes ... I've seen these 2
different structures, and I know they are functionally different, but I
don't understand why and how each should be used:

(example 1) $hashname{$key{$subkey}}

(example 2) $hashname($key}{$subkey}

Can someone explain how these structures are used?

thanks & regards,

John
 
J

jguad98

.... forgive the typo above ... "example 2" should be:
$hashname{$key}{$subkey}

John
 
A

A. Sinan Unur

(e-mail address removed) wrote in @g44g2000cwa.googlegroups.com:
This problem may be something simple that I am failing to grasp ...
I've read dozens of postings about 'hashes of hashes' and

Well, you should always start with Perl documentation rather than random
postings.

See

perldoc perlreftut

as well as

perldoc perldsc
I just can't figure out how.

Make an attempt, and we can help you with any hurdles you encounter.
(example 1) $hashname{$key{$subkey}}

(example 2) $hashname($key}{$subkey}

Example 2 is a syntax error.

Sinan
 
I

Ilmari Karonen

I have a list that I read into an array. Each element is a text string
like this (it's an object identifier):

"region.level.location.system"

Okay, you can store this in a 4-dimensional hash like this:

my %hash;
foreach my $string (@array) {
my ($region, $level, $location, $system) = split /\./, $string;
$hash{$region}{$level}{$location}{$system}++;
}

The values in the hash will simply be numbers indicating how many
times each identifier has been seen. You don't need to use them for
anything -- it's just the keys that matter.

need to generate a report that shows me the relationships in an orderly
(almost graphic) manner ... I want to be able to print out something
like this (actual formatting is not important, just the organization):

region1

region1.level1
region1.level2

region1.level1.location1
region1.level1.location2 [snip]

... so on and so forth ... iterating through every region, every
level, every location, every system

For the output you want, the simplest way would be to write four
separate loops, one for each depth. They will all look fairly
similar:

foreach my $region (sort keys %hash) {
print "$region\n";
}

foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
print "$region.$level\n";
}
}

foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
my %level = %{ $region{$level} };
foreach my $location (sort keys %level) {
print "$region.$level.$location\n";
}
}
}

foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
my %level = %{ $region{$level} };
foreach my $location (sort keys %level) {
my %location = %{ $level{$location} };
foreach my $system (sort keys %location) {
print "$region.$level.$location.$system\n";
}
}
}
}

Of course, if you wanted to do the same for a nested hash of arbitrary
depth, you could use a recursive solution:

sub keys_at_depth {
my ($hashref, $depth) = @_;
return sort keys %$hashref if $depth < 2;
my @keys;
foreach my $key (sort keys %$hashref) {
my $subhash = $hashref->{$key};
next unless ref $subhash eq 'HASH';
push @keys, map "$key.$_", keys_at_depth($subhash, $depth-1);
}
return @keys;
}

my $depth = 1;
while (my @keys = keys_at_depth(\%hash, $depth)) {
print "$_\n" for @keys;
$depth++;
}

There, that's much nicer, isn't it? Of course, there are several
other ways to approach your problem as well. For example, you could
instead use four separate hashes, one for each depth:

my (%regions, %levels, %locations, %systems);
foreach my $string (@array) {
my ($region, $level, $location, $system) = split /\./, $string;
$regions {"$region"}++;
$levels {"$region.$level"}++;
$locations{"$region.$level.$location"}++;
$systems {"$region.$level.$location.$system"}++;
}

print "$_\n" for sort keys %regions;
print "$_\n" for sort keys %levels;
print "$_\n" for sort keys %locations;
print "$_\n" for sort keys %systems;

Or you could use an array of hashes:

my @depths;
foreach my $string (@array) {
my @parts = split /\./, $string;
$depths[$_]{join ".", @parts[0 .. $_]}++ for 0 .. $#parts;
}

foreach my $depth (@depths) {
print "$_\n" for sort keys %$depth;
}

There, I don't think it gets any more compact than that. Of course,
to populate @array, you'd use:

my @array = <>;
chomp @array;

and don't forget to start your script with:

#!/usr/bin/perl
use warnings;
use strict;
 
J

jguad98

My first posting of 'example 2' has a paren where there should be a
curly, but to match the actual code I found in a script, it should be
like this:

$hashname{$key}{bareword} = 'value';

I know this structure is legal (well, it works). I extrapolated that
'bareword' could be replaced with a variable, hence it should work as

$hashname{$key}{$subkey} = 'value';

What is the functional difference between that and

$hashname{$key{$subkey}} = 'value';

?
.....

I scanned the perldocs perllol and perldsc ... the latter one helped
immensely ... I now have a working script using the HoH structure as
in example 1. Thank you, Sherm & Sinan.
 
A

A. Sinan Unur

(e-mail address removed) wrote in @f14g2000cwb.googlegroups.com:
What is the functional difference between that and

$hashname{$key{$subkey}} = 'value';

Does this help?

my %key = (
# blah blah
);
my $x = $key{$subkey};

my %hashname = (
# blah blah
);

$hashname{$x} = 'value';

Now, instead of $x, use $key{$subkey}:

$hashname{$key{$subkey}} = 'value';

which is different than

$hashname{$key}{$subkey} = 'value';

Indeed, the latter is the case where the names $key and $subkey convey
the intended meaning.

You should use Data::Dumper and check out the structure you end up with
following various operations.

And please quote an appropriate amount of context when you reply.

Sinan
 
E

Eric Bohlman

(e-mail address removed) wrote in @f14g2000cwb.googlegroups.com:
I know this structure is legal (well, it works). I extrapolated that
'bareword' could be replaced with a variable, hence it should work as

$hashname{$key}{$subkey} = 'value';

What is the functional difference between that and

$hashname{$key{$subkey}} = 'value';

?

In the first case, you have a hash (%hashname) and you're "subscripting" it
with two scalar variables, $key and $subkey. In the second case, you have
*two* hashes (%hashname and %key) and only one scalar ($subkey); you are
*not* making any use of a scalar called $key. Instead you're looking up
the value for $subkey in %key, and then using that value as a key to set
the corresponding value in %hashname. Two completely different operations.
 
T

Tad McClellan

Eric Bohlman said:
(e-mail address removed) wrote in @f14g2000cwb.googlegroups.com:


In the first case, you have a hash (%hashname) and you're "subscripting" it
with two scalar variables, $key and $subkey. In the second case, you have
*two* hashes (%hashname and %key) and only one scalar ($subkey); you are
*not* making any use of a scalar called $key. Instead you're looking up
the value for $subkey in %key, and then using that value as a key to set
the corresponding value in %hashname. Two completely different operations.


And the first one makes use of references, while there are no
references involved with the second one.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top