carcassonne has asked for the wisdom of the Perl Monks concerning the following question:
Hi all !
I've made a structure that uses 3 levels of hashes. It works nice but creating these levels requires a lot, and I mean a lot, of typing per each element. See:
$project{$activeProject}->{components}->{"Software"}->{subComponents}-
+>{"Database"}->{label} = "Test-1.01";
That's one element. And it has a lot of elements at different levels. There must be a way in Perl to reduce this amount of typing while retaining the structure. I tried equating part of it, like this:
sub readItem {
my $subDatabase = $project{$activeProject}->{components}->{"Software"}
+->{subComponents}->{"Database"};
print "$subDatabase->{label}";
}
And while this works when printing elements, it simply does not write anything to the structure when used to initialize data (I think that's because $subDatabase is local to a subroutine...)
Anyhow, I'd really appreciate any suggestions regarding typing less to initialize data in such extensive structures. I'd go for an object-oriented approach if needed.
Cheers.
Re: Using multi-level hashes
by QM (Parson) on Oct 28, 2005 at 22:32 UTC
|
If you really need to type things in manually, try this:
my %hash =
( "Abe" =>
{ "lastname" => "Lincoln",
"friends" =>
{ "George" =>
{ "lastname" => "Washington",
"wife" => "Martha"
}
"James" =>
{ "lastname" => "Madison",
"wife" => "Dolly"
}
},
},
"Franklin" =>
{ "lastname" => "Roosevelt",
"wife" => "Eleanor"
}
)
However, if you have some structure, you'll want to stuff these programmatically, instead of retyping all of the keys.
There are several ways to do this. The most direct is to create a sub to navigate the hash for you. The sub will take a hash ref, a value, and a list of keys. The value may itself be a hash(ref), so you can nest these and use loops and so forth.
For example:
#!/your/perl/here
use strict;
use warnings;
use Data::Dumper;
sub update_hash
{
my ($h, $value, @keys) = @_;
for my $key (@keys[0..$#keys-1])
{
$h = $h->{$key};
}
$h->{$keys[-1]} = $value;
}
# build up a hash from scratch
my %hash;
update_hash(\%hash, {}, 'one');
update_hash(\%hash, {}, 'one','two');
update_hash(\%hash, 123, 'one','two','three');
# don't have to start at the top
update_hash($hash{one}{two},129,'nine');
update_hash($hash{one}{two},{},'zero');
update_hash($hash{one}{two},1208,'zero','eight');
print Dumper(\%hash);
__OUTPUT__
$VAR1 = {
'one' => {
'two' => {
'three' => 123,
'zero' => {
'eight' => 1208
},
'nine' => 129
}
}
};
The OO approach is really just a group of setters/getters that grok your data structure. If you have a complicated and deeply-nested structure, you might want to do that just for your sanity -- otherwise, it's probably better just to write subs as needed.
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [d/l] [select] |
Re: Using multi-level hashes
by shemp (Deacon) on Oct 28, 2005 at 21:39 UTC
|
As for your first question of how to reduce the amount of typing, it very much depends on what you want to do with your structure. If you're populating it, you probably have some simpler structures that contain the data being entered into the 3-level hash. Loop through them. Or if you're manually adding each elemnt, you're somewhat stuck.
For the second part, in relation to your readItem() function, and assigning to the structure, you have multiple problems. First off, you should pass you structure in to the function, instead of using it as a global, i.e.:
...
# populate your %project
readItem(\%project);
...
sub readItem {
my ($project) = @_;
my $sub_project = $project->{...}->{...};
etc.
}
If that doesn't make sense, you need to read more about variable scope.
As for assigning into a referenced subpart, the important thing to consider is what just got deferenced when you created the sub part. Is it a hashref thats also pointed to by your big structure, or is it a scalar, or is it a newly created anonymous structure?
my %multi_level = (
'foo' => { 1 => 'A', 2 => 'B' },
'bar' => { 'C' => 'see', 'T' => 'tea' },
);
my $sub_part = $multi_level{foo}; # sub_part is a hashref,
# also pointed at by $multi_level{foo}
print $multi_level{foo}->{1} . "\n"; # works (prints 'A')
$sub_part->{3} = 'C';
print $multi_level{foo}->{3} . "\n"; # works (prints 'C')
my $part_two = $multi_level{foo}->{1}; # part_two is a
# scalar, a copy of the value of $multi_level{foo}->{1},
# namely 'A'. Changing it will not change %multi_level
the $part_two example may make more sense if you think of this:
my $a = 'blah';
my $b = $a;
$b = 'foo';
print "$a\n"; # prints 'blah'
print "$b\n"; # prints 'foo'
I use the most powerful debugger available: print!
| [reply] [d/l] [select] |
Re: Using multi-level hashes
by Roy Johnson (Monsignor) on Oct 28, 2005 at 21:32 UTC
|
For a tiny bit of help, you can omit the arrows between braces.
I'm not clear on what problem you're having in using a ref for initialization. Could you amend your post to include a few lines of your initialization code? It's certainly possible to do something like
my $this_level = $project{$activeProject}{components}{"Software"}{subC
+omponents}{"Database"};
$this_level->{label} = 'Test-1.01';
$this_level->{some_other_tag} = 'You passed';
Caution: Contents may have been coded under pressure.
| [reply] [d/l] |
Re: Using multi-level hashes
by Zed_Lopez (Chaplain) on Oct 28, 2005 at 21:32 UTC
|
$project{$activeProject}{components}{"Software"}{subComponents}{"Database"}{label} is syntactically equivalent to $project{$activeProject}->{components}->{"Software"}->{subComponents}->{"Database"}->{label}.
The $project hash's values are hashrefs. You can modify the values of those hashrefs within a subroutine.
my %h;
$h{a}{b}{c} = 'd';
print "$h{a}{b}{c}\n";
f($h{a}{b});
print "$h{a}{b}{c}\n";
sub f {
my $x = shift;
$x->{c} = 'e';
}
produces
d
e
| [reply] [d/l] [select] |
Re: Using multi-level hashes
by friedo (Prior) on Oct 28, 2005 at 21:36 UTC
|
It's looks like you've got at least five levels there, not three. First, here's a tip that will save you a few keystrokes. Once you're "in" a complex structure, you don't need to type the -> operator to dereference. So $project{$activeProject}->{components}->{"Software"}->{subComponents}-
>{"Database"}->{label}
can be written $project{$activeProject}{components}{Software}{subComponents}{Database}{label}
You were on track for adding elements to the hash without typing the entire structure each time. To add stuff to that final level, you can do:
$subDatabase->{label} = "whatever";
$subDatabase->{foobar} = "something else";
Since $subDatabase is a reference to the original structure, the changes will persist after you leave the sub (assuming %project is defined outside the sub's scope, as it appears.)
Note: It's always a good idea, but especially when working with complex data structures, to turn strict and warnings on, if you haven't already. | [reply] [d/l] [select] |
Re: Using multi-level hashes
by ioannis (Abbot) on Oct 29, 2005 at 01:28 UTC
|
Your readItem() subroutine suggests the need for access to
values without much typing. If you also desire to assign
new values, why not use an alias? Like so:
*label= \$project{$activeProject}
{components}
{Software}
{subComponents}
{Database}
{label} ;
You read the value using print $lable;,
and assign a new value using '$label='Test-2'; .
This is a lot shorter than the OP which exhibited the
full train of variables, and was coded like this:
$project{$activeProject}
{components}
{Software}
{subComponents}
{Database}
{label}
= 'Test-2';
| [reply] [d/l] [select] |
Re: Using multi-level hashes
by metaperl (Curate) on Oct 28, 2005 at 22:03 UTC
|
Don't forget the work of a Perl Grand Wizard:
Alias
| [reply] |
Re: Using multi-level hashes
by zentara (Archbishop) on Oct 29, 2005 at 11:48 UTC
|
Maybe you have reached the "tipping point" where it is advantageous to use packages and namespaces. You could make a Project namespace, and a method to set/get it's label. Like
$activeProject->set_label('Test-1.01');
I'm not really a human, but I play one on earth.
flash japh
| [reply] [d/l] |
Re: Using multi-level hashes
by JamesNC (Chaplain) on Oct 29, 2005 at 20:58 UTC
|
Here is an example of an oop approach you may find useful.
package component;
my %sc;
sub new { bless { name => "$_[1]" }, 'component'; }
sub name { $_[0]->{name}; }
sub sc {
$sc{$_[0]->name} = [] unless $sc{$_[0]->name};
push @{$sc{$_[0]->name}}, $_[1];
$_[0]{$_[1]} = new component($_[1]);
};
sub label:lvalue { $_[0]->{lbl}; }
sub subs { \@{$sc{$_[0]->name}}; }
1;
package project;
sub new { bless { }, 'project'; }
sub components { my @c; @c = sort keys %{$_[0]}; \@c; }
sub addcomponent {
$_[0]->{$_[1]} = new component( $_[1] );
$_[0]->{$_[1]};
}
sub show {
my $proj = shift;
foreach my $comp ( @{$proj->components} ){
print "$comp\n";
next unless $#{$proj->{$comp}->subs} > 0;
foreach my $sc ( @{$proj->{$comp}->subs} ){
next unless $proj->{$comp}{$sc};
print "\t$sc\t" if $sc;
print $proj->{$comp}{$sc}->label,"\n"
if $proj->{$comp}{$sc}->label;
}
}
}
1;
use strict;
my $proj = new project();
my $i = 0;
while( <DATA> ){
$i++; chomp;
next if $i == 1;
my ($comp, $subcomp, $label ) = split /\t/, $_;
my $sw = $proj->{$comp};
$sw = $proj->addcomponent( $comp ) unless $sw;
my $sc = $sw->sc($subcomp);
$sc->label = $label;
}
$proj->show;
my $hw = join ', ', @{$proj->{Hardware}->subs};
print "Hardware subcomponents ( $hw )\n";
my $sw = $proj->{Software};
my $db = $sw->{Database};
my $label = $db->label;
print "$sw->{name} $db->{name} $label\n";
__DATA__
COMPONENT SUBCOMPONENT LABEL
Software Database Test-1.01
Software Good Languages Perl
Software Sucky Languages Java
Hardware Monitor 19" LCD
Hardware CPU Pentium 2.4 Ghz
Cheers,
JamesNC | [reply] [d/l] |
Re: Using multi-level hashes
by perlfan (Vicar) on Nov 01, 2005 at 05:07 UTC
|
This may be completely offbase, but you may want to look into DBM::Deep. I have found it very helpful in the past when dealing with these types of data structures; it also allows you to persist hashes to disk (as will Data::Dumper, among others).
pF | [reply] |
|
|