Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Looping through Multi Dimensional Hashes

by CodeJunkie (Monk)
on Jul 15, 2003 at 12:42 UTC ( [id://274383]=perlquestion: print w/replies, xml ) Need Help??

CodeJunkie has asked for the wisdom of the Perl Monks concerning the following question:

Hi,
I have a multi-dimensional hash like this:

Syntax: $category_hash{item_id}{parent_id}{name} i.e. $category_hash{1}{0}{name} $category_hash{2}{1}{name} $category_hash{3}{1}{name} $category_hash{4}{2}{name}

The idea is to have a data structure to represent categories and sub categories.

For example, $category_hash{1}{0}{name} is a top level category and $category_hash{2}{1}{name} is a sub category within it, does that make sense?

I need to know how I can loop through the entire category_hash.

foreach my $key (keys %category_hash) { $category_dropdown.="<tr><td><a href=\"auction.cgi?category=$key\" +>$category_hash{$key}{0}{name}</a></td></tr>"; }

This will loop through the basic level keys, but what about the second level keys? (not sure i've used the correct terminology there actually).

Appreciate any help you can give me regarding multi dimensional hashes - they are doing my head in ;-)

Cheers,
Tom

Replies are listed 'Best First'.
Re: Looping through Multi Dimensional Hashes
by davorg (Chancellor) on Jul 15, 2003 at 13:15 UTC

    I think you've rather shot yourself in the foot with that data structure. It really makes it difficult to access the second level categories that are children of a particular category.

    Personally I'd use the flexibility of Perl data structures to my advantage and build a structure like this:

    #!/usr/local/bin/perl use strict; use warnings; my %category_hash = ( 1 => { name => 'whatever', children => { 2 => { name => 'something', children => { 3 => { name => 'another subcategory' } } } } }, 4 => { name => 'something else' } );

    You can then display the structure with code like this:

    show(\%category_hash, 0); sub show { my ($hash, $lvl) = @_; my $prefix = ' ' x $lvl; foreach (sort keys %$hash) { print "$prefix$_ : $hash->{$_}{name}\n"; show($hash->{$_}{children}, ++$lvl) if exists $hash->{$_}{children}; } }

    But you should also look at the Tree modules from CPAN.

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      Thanks for the reply, I think you might have a point ;-) I kinda thought that mid-way through coding...

      The main problem is that I am getting values from a database. The table looks like this:

      id,name,description,parent_id

      Of course I could change this table structure, but I thought it should hold all the values ok. I am then reading from the database like so:

      sub getCategories() { my ($dbh)=@_; my $sth=$dbh->prepare("SELECT categories.id, categories.name, categories.description, categories.parent_id FROM categories"); $sth->execute(); my %cat_hash; while (my $cat_hashref = $sth->fetchrow_hashref()) { if ($cat_hashref->{parent_id}) { $cat_hash{$cat_hashref->{id}}{$cat_hashref->{parent_id}}{name}=$ +cat_hashref->{name}; $cat_hash{$cat_hashref->{id}}{$cat_hashref->{parent_id}}{descrip +tion}=$cat_hashref->{description}; $cat_hash{$cat_hashref->{id}}{$cat_hashref->{parent_id}}{parent_ +id}=$cat_hashref->{parent_id}; } else { $cat_hash{$cat_hashref->{id}}{0}{name}=$cat_hashref->{name}; $cat_hash{$cat_hashref->{id}}{0}{description}=$cat_hashref->{des +cription}; $cat_hash{$category_hashref->{id}}{0}{parent_id}='0'; } } $sth->finish(); return %cat_hash; }

      Thanks for your help, I will check out the Tree modules, maybe then can shed some light on the situation for me.

      Appreciate any comments people might have on the above code.

      cheers,
      Tom

        OK, so lets see who we can build up a data structure like mine from your database. I'm going to read it from the DATA filehandle as I don't have time to set up a database, but the logic is just the same.

        #!/usr/local/bin/perl use strict; use warnings; my %category_hash; my %categories; # Build a hash containing all of our data records... while (<DATA>) { chomp; my %cat; @cat{'id','name','parent'} = split /,/; $categories{$cat{id}} = \%cat; } # ... and turn it into a tree foreach $_ (keys %categories) { if ($categories{$_}{parent}) { $categories{$categories{$_}{parent}}{children}{$_} = $categories{$_}; } else { $category_hash{$_} = $categories{$_}; } } # And now %category_hash is exactly the same as it was # in my previous post. Carry on with "show" as before. __DATA__ 1,whatever,0 2,something,1 3,another subcategory,2 4,something else,0
        --
        <http://www.dave.org.uk>

        "The first rule of Perl club is you do not talk about Perl club."
        -- Chip Salzenberg

Re: Looping through Multi Dimensional Hashes
by gjb (Vicar) on Jul 15, 2003 at 12:51 UTC

    Hm, it seems like you're actually trying to code a tree, so I think life would be easier if you simply did it that way.

    There are several tree modules on CPAN, and I can recommend Tree::MultiNode which is pretty complete, flexible and easy to use.

    Hope this helps, -gjb-

      Yeah thanks that is what I am trying to do, I will check out the module.

      Also found code and adapted to create this:

      foreach my $key1 (keys %category_hash) { foreach my $key2 (keys %{$category_hash{$key1}}) { if ($category_hash{$key1}{$key2}{name} eq '0') { $category_dropdown.="<p>$category_hash{$key1}{$key2}{name} Is +a top Level category</p>"; } else { $category_dropdown.="<p>$category_hash{$key1}{$key2}{name} Is +a sub Level category</p>"; } } }

      Take a look at the hash code here

Re: Looping through Multi Dimensional Hashes
by l2kashe (Deacon) on Jul 15, 2003 at 14:37 UTC
    Davorg's post above is on point, but here is how I deal with datastructures like this. Basically I localize a hash ref, to make the code slightly more visually appealing, ala..
    for ( keys %category_hash ) { my $first_lvl = $category_hash{$_}; print "$_\n"; for ( keys %{ $first_lvl } ) { my $second_lvl = $first_lvl->{$_}; print ' ' x 3 . "$_"\n"; for ( keys %{ $second_lvl } ) { print ' ' x 6 . "$_ : $second_lvl->{$_}\n"; } } # END for keys $first_lvl } # END for keys %category_hash
    Grabbing a ref to the hash will be slightly better than creating a copy of the sub hashes as you go along. Personally I think it might be better to define a better structure like recommended above.

    Regards

    MMMMM... Chocolaty Perl Goodness.....

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2024-04-25 19:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found