Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Using multi-level hashes

by carcassonne (Pilgrim)
on Oct 28, 2005 at 21:01 UTC ( [id://503766]=perlquestion: print w/replies, xml ) Need Help??

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.

Replies are listed 'Best First'.
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

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!
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.
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
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.

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';
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
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
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
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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://503766]
Approved by ww
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (7)
As of 2024-03-28 22:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found