Search code examples
perlhashmapdereferenceperl-data-structuresmulti-level

printing out a multilevel hash perl 5


I have to make sense of this script , without making major changes and insulting the guy who made it. I can't change the hashes, even though it would be easy to load data into arrays and then split. The guy who wrote this (my boss) loves his multilevel hashes. I have to print out the %extend_hash multilevel hash - and I don't understand how to get to the last level. I need to print this out in to a CSV file that cane be read by salespeople. It looks like it goes out like 6 levels. I have to sort keys of a hash, of a hash, of a hash ...etc.

#!/scripts/perl/bin/perl
use strict;
use warnings;
use DBI;

my $dbUser = 'foo_01';
my $dbPass = 'foo_01';
my $dbSid  = 'foo.WORLD';
my $dbh =  DBI->connect("dbi:Oracle:$dbSid","$dbUser","$dbPass") or die( "Couldn't connect: $!" );

#sub read_extend

my %extend_hash = ();
my $query = "select level_id,e_risk_symbol,e_exch_dest,penny,specialist from etds_extend";
if(!$dbh) {
    print "Error connecting to DataBase; $DBI::errstr\n";
        }
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement: ".$dbh->errstr;
$cur_msg->execute();
while (my @row=$cur_msg->fetchrow_array) {
    $extend_hash{$row[0]}{$row[1]}{$row[2]}{'penny'}=$row[3];
    $extend_hash{$row[0]}{$row[1]}{$row[2]}{'specialist'}=$row[4];
}

for my $what_row0 (sort keys %extend_hash) {
    for my $what_row1 (sort keys %{$extend_hash {$what_row0} }) {
        for my $what_row2 (sort keys ..... I am lost.

I don't know how to print out the %extend_hash down to the lowest level I am trying to make it comma delimited, able to be pumped into an email and read by salespeople.

6,ACI,ARCX,specialist,1
6,ACI,ARCX,penny,0
6,MCHP,ARCX,specialist,1,
6,MCHP,ARCX,penny,0
6,BC,AMXO,specialist,1
6,BC,AMXO,penny,0
6,WM,XISX,specialist,1
6,WM,XISX,penny,0
6,PK,AMXO,specialist,1
6,PK,AMXO,penny,0
6,SPLS,XISX,specialist,1
6,SPLS,XISX,penny,0

If I use Data::Dumper I get this which is great, but the sales/marketing guys will get confused. They will not be able to see ARCX penny0 in realtions to the group '6'. I don't think they are able to mentally walt through the data::dump

$VAR1 = {
          '6' => {
                    'IACI' => {
                               'ARCX' => {
                                            'specialist' => '1',
                                            'penny' => '0'
                                          }
                             },
                   'MCHP' => {
                                'ARCX' => {
                                           'specialist' => '1',
                                            'penny' => '0'
                                         }
                             },
                    'BC' => {
                             'AMXO' => {
                                         'specialist' => '1',
                                         'penny' => '0'
                                       }
                            },
                    'WM' => {
                             'XISX' => {
                                         'specialist' => '1',
                                         'penny' => '0'
                                       }
                            },
                   'PKD' => {
                              'AMXO' => {
                                           'specialist' => '1',
                                           'penny' => '0'
                                         }
                            },
                    'SPLS' => {
                               'XISX' => {
                                           'specialist' => '1',
                                           'penny' => '0'
                                 }
                    }
          } 
};

update - amazing work I_alarmed_alien - this should be a good stackoverflow reference (my comments are disabled)

for my $level_1 (sort keys %extend_hash) {
     for my $level_2 (sort keys %{$extend_hash{$level_1} }) {
        for my $level_3 (sort keys %{$extend_hash{$level_1}{$level_2}}) {
            for my $type (sort keys %{$extend_hash{$level_1}{$level_2}{$level_3}} ) {
                print "$level_1, $level_2, $level_3 $type" . " $extend_hash{$level_1}{$level_2}{$level_3}{$type}" ."\n" ;
                }
         }
    }
}

got it - I_alarmed_alien amazing work outputing a hash of a hash of a hash of a hash of a hash of a hash

6, XLNX, AMXO , specialist 1
6, XLP, AMXO , penny 0
6, XLP, AMXO , specialist 1
6, XLP, XISX , penny 0
6, XLP, XISX , specialist 1
6, XLV, AMXO , penny 0
6, XLV, AMXO , specialist 1
6, XLY, AMXO , penny 0
6, XLY, AMXO , specialist 1
6, YUM, AMXO , penny 0
6, YUM, AMXO , specialist 1
6, ZINC, XISX , penny 0
6, ZINC, XISX , specialist 1
6, ZMH, AMXO , penny 0
6, ZMH, AMXO , specialist 1

Solution

  • You're almost there with your code. Here is how to get to the bottom of the hash:

    foreach my $l1 (keys %extend_hash) {
        # '6'
        foreach my $l2 (keys %{$extend_hash{$l1}}) {
            # IACI, MCHP, BC, etc.
            foreach my $l3 (keys %{$extend_hash{$l1}{$l2}}) {
                # ARCX, AMXO, XISX, etc.
                foreach my $k (keys %{$extend_hash{$l1}{$l2}{$l3}}) {
                    print "$l1, $l2, $l3, $k, " . $extend_hash{$l1}{$l2}{$l3}{$k} . "\n";
                }
            }
        }
    }
    

    Note that the hash keys are not accessed in any particular order, so you may want to sort them -- e.g. foreach my $l1 (sort keys %extend_hash).

    Hashes of hashes of hashes of hashes are fun! ;)

    ETA: Here is a more generic function for recursing into arbitrarily deep hashes-of-hashes-of-hashes-of...

    sub print_hash {
        # href = reference to the hash we're examining (i.e. \%extend_hash)
        # so_far = arrayref containing the hash keys we are accessing
        my $href = shift;
        my $so_far = shift;
        foreach my $k (keys %$href) {
            # put $k on to the array of keys
            push @$so_far, $k;
            # if $href->{$k} is a reference to another hash, call print_hash on that hash
            if (ref($href->{$k}) eq 'HASH') {
                print_hash($href->{$k}, $so_far);
            } else {
            # $href->{$k} is a scalar, so print out @$so_far (our list of hash keys)
            # and the value in $href->{$k}
                print join(", ", @$so_far, $href->{$k}) . "\n";
            }
            # we've finished looking at $href->{$k}, so remove $k from the array of keys
            pop @$so_far;
        }
    }
    
    print_hash($hash, []);