Building HoHoH from arbitrary size arrays ?

A

Allan Houston

Hi,

I've searched and read about everything I can find on trying to do this,
but my only solution I can think of is real, real ugly..

Basically I'm looking to create a sub routine which will query a
database table for the specified rows, and create a hash where each
column is a key in the hash of hashes.

i.e. From a table like so :

+---------------+---------------+------------+------+
| region | location | function | type |
+---------------+---------------+------------+------+
| Aztec West | Aztec West | PE Routers | 7513 |
| Aztec West | Staverton | PE Routers | 7513 |
| Barnsley | Barnsley | PE Routers | 7513 |
| Barnsley | Bradford | PE Routers | 7513 |
| Barnsley | Sheffield | PE Routers | 7513 |
| Basildon | Basildon | PE Routers | 7513 |
| Basildon | Gillingham | PE Routers | 7513 |
-----------------------------------------------------

I'd produce a hash like :

$hash{'Aztec West'}{'Aztec West'}{'PE Routers}{'7513'}
$hash{'Aztec West'}{'Staverton'}{'PE Routers}{'7513'}
$hash{'Barnsley'}{'Barnsley'}{'PE Routers'}{'7513'} etc etc.

The problem being that I'm trying to create a function which will read
in say the first $count columns and return a hash of hashes with a depth
of $count keys.

The only way I can think to do it so far is something like this :

------------------------------------------------------------------------

while (@t=$query->fetchrow_array())
{

if (@t == 1)
{ $hash{$t[0]}=''; }
elsif (@t == 2)
{ $hash{$t[0]}{$t[1]}='';}
elsif (@t == 3)
{ $hash{$t[0]}{$t[1]}{$t[2]}='';}
elsif (@t == 4)
{ $hash{$t[0]}{$t[1]}{$t[2]}{$t[3]}='';}
elsif (@t == 5)
{ $hash{$t[0]}{$t[1]}{$t[2]}{$t[3]}{$t[4]}='';}
elsif (@t == 6)
{ $hash{$t[0]}{$t[1]}{$t[2]}{$t[3]}{$t[4]}{$t[5]}='';}

}

return(\%hash);

------------------------------------------------------------------------

There has to be a more elegant solution than this surely.. Can anyone
point me in the right direction please ?

Cheers,
Allan.
 
P

Paul Lalli

Allan said:
I've searched and read about everything I can find on trying to do this,
but my only solution I can think of is real, real ugly..

Basically I'm looking to create a sub routine which will query a
database table for the specified rows, and create a hash where each
column is a key in the hash of hashes.
The problem being that I'm trying to create a function which will read
in say the first $count columns and return a hash of hashes with a depth
of $count keys.

I strongly suspect an AB problem here. Why exactly do you feel the
need to create such a function? What is your *actual* goal?
There has to be a more elegant solution than this surely.. Can anyone
point me in the right direction please ?

I don't understand *why* you'd want to do this, but I believe this
meets your criteria:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @stuff = qw/foo bar baz bam boom/;

my %h;
my $ref = \%h;
for my $level (@stuff){
$ref->{$level} = {};
$ref = $ref->{$level};
}

print Dumper(\%h);

__END__


$VAR1 = {
'foo' => {
'bar' => {
'baz' => {
'bam' => {
'boom' => {}
}
}
}
}
};




Paul Lalli
 
A

Allan Houston

Paul said:
I strongly suspect an AB problem here. Why exactly do you feel the
need to create such a function? What is your *actual* goal?




I don't understand *why* you'd want to do this, but I believe this
meets your criteria:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @stuff = qw/foo bar baz bam boom/;

my %h;
my $ref = \%h;
for my $level (@stuff){
$ref->{$level} = {};
$ref = $ref->{$level};
}

print Dumper(\%h);

__END__


$VAR1 = {
'foo' => {
'bar' => {
'baz' => {
'bam' => {
'boom' => {}
}
}
}
}
};




Paul Lalli

Hi Paul,

First off - thanks for your code, its certainly helped me.

The reason I'm doing it this way is that the DBI fetchrow_hashref will
overwrite values where the key has multiple values which is how my data
is stored (unfortunately..)

The sub I'm writing allows me to specify an array of arbitrary columns
and get them returned in a hash hierarchy, which I find useful for
getting at the data quickly and easily.

Far more importantly however, you've shown me how to do something in
perl which I didn't know before - thank you :)

I made some minor changes to the code you provided to cope with the
multiple key values :


#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @stuff = ([qw/foo bar baz bam boom/],[qw/foo bar wizz wham whallop/]);

my %h;
my $ref = \%h;

foreach my $stuff_ref (@stuff) {
for my $level (@$stuff_ref) {

if (exists $ref->{$level}) { # If the key exists then just move on

$ref = $ref->{$level};
next;
}

$ref->{$level} = {};
$ref = $ref->{$level};
}

$ref = \%h; # Start at the beginning again

}

print Dumper(\%h);

Which seems to work OK. I probably kludged up your beautiful code -
please accept my humble apologies if this is the case.

# ./hashes.pl
$VAR1 = {
'foo' => {
'bar' => {
'baz' => {
'bam' => {
'boom' => {}
}
},
'wizz' => {
'wham' => {
'whallop' => {}
}
}
}
}
};


Cheers,
Allan.
 
P

Paul Lalli

Allan said:
The reason I'm doing it this way is that the DBI fetchrow_hashref will
overwrite values where the key has multiple values which is how my data
is stored (unfortunately..)

Out of curiousity, have you looked at the fetchall_hashref method?
That one reads the entire table, and allows you to specify multiple key
columns.
The sub I'm writing allows me to specify an array of arbitrary columns
and get them returned in a hash hierarchy, which I find useful for
getting at the data quickly and easily.

Far more importantly however, you've shown me how to do something in
perl which I didn't know before - thank you :)

I made some minor changes to the code you provided to cope with the
multiple key values :


#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @stuff = ([qw/foo bar baz bam boom/],[qw/foo bar wizz wham whallop/]);

my %h;
my $ref = \%h;

foreach my $stuff_ref (@stuff) {
for my $level (@$stuff_ref) {

if (exists $ref->{$level}) { # If the key exists then just move on

$ref = $ref->{$level};
next;
}

$ref->{$level} = {};
$ref = $ref->{$level};
}

$ref = \%h; # Start at the beginning again

}

print Dumper(\%h);

Which seems to work OK. I probably kludged up your beautiful code -
please accept my humble apologies if this is the case.

I would just add a statement modifier to the body of the for loop,
rather than the if block and duplicate statements:
for my $level (@stuff){
$ref->{$level} = {} unless exists $ref->{$level};
$ref = $ref->{$level};
}

Paul Lalli
 
B

Brian McCauley

Paul said:
for my $level (@stuff){
$ref->{$level} = {} unless exists $ref->{$level};
$ref = $ref->{$level};
}

When building such a HoHoH there's no way that $ref->{$level} will
exist but be false. As such you don't need the exists() in there...

$ref->{$level} = {} unless $ref->{$level};

....which, of course, simlifies to...

$ref->{$level} ||= {};

....which in turn makes it easy to combine it with the next statement...

$ref = $ref->{$level} ||= {};

....although I tend to use autovivifivation instead...

$ref = \%{$ref->{$level}};

And given a short single statement I tend to opt for the statement
qualifier version of for.

$ref = \%{$ref->{$_}} for @stuff;

In practice you often don't want the bottom level of such a HoHoH to be
an empty hash so I usually use a refererence to reference instead.

my $ref = \\%h;
$ref = \$$ref->{$_} for @stuff;
 

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

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top