Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked

A better way for walking through HoHoH ?

by ZlR (Chaplain)
on Mar 24, 2010 at 10:14 UTC ( #830512=perlquestion: print w/replies, xml ) Need Help??
ZlR has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,

Most of the time i parse data results from various system commands, consolidate everything into One Big Hash and then build from it various things i need, mostly other system commands. Sometimes it gets a little messy, and right now i have this structure :

'host1' => { 'tgt' => { 'target_0011' => { '11C3' => '0C5C', '039F' => '14C4', '11D3' => '0C6C', '11F3' => '0C8C', '11B3' => '0C4C', '11E3' => '0C7C' } }, 'wwn' => [ '10000000c95ffff1', '10000000c96ffff2' ] },
Right now i only have one 'tgt' but it could change, so i end up doing :
foreach my $host (keys %rez) { foreach my $tgt ( keys %{ $rez{$host}{'tgt'} } ) { foreach my $dev ( keys %{ $rez{$host}{'tgt'}{$tgt} } ) { do something with $tgt, $dev and $rez{$host}{'tgt'}{$tgt}{$dev} }}}
Those 3 foreach, y'know, i don't like it :P
Is there a better way to do this ? Possibly something completely different ?

Thanks !

Replies are listed 'Best First'.
Re: A better way for walking through HoHoH ?
by roboticus (Chancellor) on Mar 24, 2010 at 11:24 UTC


    I sometimes use this trick: Rather than reference the top hash everywhere, I create a reference to the "current" hash in each level of the loop, like so:

    #!/usr/bin/perl -w use strict; use warnings; my %rez = ( 'host1' => { 'tgt' => { 'target_0011' => { '11C3' => '0C5C', '11E3' => '0C7C' }, }, 'wwn' => [ 'foobar' ] }, ); for my $curHost (keys %rez) { my $host = $rez{$curHost}; # Use $$host{key} at this level print "HOST: $curHost\n"; for my $curDev (keys %{$$host{tgt}}) { my $dev = $$host{tgt}{$curDev}; # Use $$dev{key} at this level # without using trick: my $bar = $rez{$curHost}{tgt}{$curDev}{'11E3'}; # with trick: my $foo = $$dev{'11C3'}; print " DEV $curDev contains: ", join(", ", sort keys %$dev +), "\n"; } }

    This way, your references aren't a foot and a half long. You can easily add or remove a level of your structure pretty easily, too, since you needn't edit a bazillion lines of code--only the ones where you change levels. I normally use this trick when (a) I suspect that I'll have to rearrange the structure or (b) my references get too long.


      Yep, now i remember :) that's like what jethro said, but with names, and will really make things clearer.
Re: A better way for walking through HoHoH ?
by cdarke (Prior) on Mar 24, 2010 at 10:54 UTC
    Using keys and a foreach loop will create a temporary list of all the keys. With small hashes that probably won't make much difference, but an alternative way is to use each which returns the key and value on each iteration in a while loop, for example:
    while (my ($host, $value) = each(%rez)) { # More nested while loops }
    update corrected with each in code (thanks Hue-Bond)
Re: A better way for walking through HoHoH ?
by jethro (Monsignor) on Mar 24, 2010 at 10:38 UTC

    Aside from the aesthetic aspect there is practically no problem with loops that only loop once or twice.

    To shorten later access you could change the loops to

    foreach my $host (keys %rez) { foreach my $tgtp ( values %{ $rez{$host}{'tgt'} } ) { foreach my $dev ( keys %$tgtp ) { #do something with $tgtp->{$dev}

    You could also use map instead of foreach loops, but that would be just window dressing as map is just another type of loop

      Ahhh yes ! i forgot that could be done, ie %$ref ! it will clean things up a lot !
Re: A better way for walking through HoHoH ?
by RMGir (Prior) on Mar 24, 2010 at 13:12 UTC
    Recursion to the rescue! :)
    sub handleRefs { my ($target, $keys_ar) = @_; if (ref $target eq "HASH") { foreach my $key(keys %$target) { handleHashOrElement($target->{$key},[@$keys_ar,$key]); } return; } if (ref $target eq "ARRAY") { print "ARRAY at @$keys_ar is [ @$target ]\n"; return; } print "Value at @$keys_ar is $target\n"; } handleRefs(\%rez, []);
    In reality, you'd probably pass in another sub reference which would be the action to invoke on the final values.

      Ok, so now it really feels like there is a method on an object waiting in the shadow here :D

      I will look at this code more closely when i can !

Re: A better way for walking through HoHoH ?
by JavaFan (Canon) on Mar 24, 2010 at 10:32 UTC
    A three level structure you want to walk on all levels. Three simple loops; I don't think it gets any better than that.

    But then, who knows? What's *your* notion of "better"?

      Actually i was wondering if i could do without using a hash ^^
      I've been doing this hash trick for a long time now, but i could use a flat db file then do sql request to get what i want from it. It's a long shot tho given my below average sql skills !
        Actually i was wondering if i could do without using a hash
        Uhm, so, you have a hash of hashes of hashes, and your idea of a "better" way of walking through them is to not use hashes?

        Why? I guess you could use a serializer to turn your datastructure into a big string, and use regular expressions or index/substr to parse it, but I fail to see why you'd qualify that as "better".

        If you ever want to play around with this approach, there is a flat DB that works with CSV files. You will need:

        This is fine for small, simple DB. Its of course slower than the "real thing" and doesn't have fancy features, but I've used it before with good results. Since the DB is a CSV file you can look at it in Excel or whatever. And since it uses standard SQL statements, you can migrate to a different DB later if needed.

Re: A better way for walking through HoHoH ?
by ikegami (Pope) on Mar 24, 2010 at 17:00 UTC
    You're going to have three loops no matter what. You seem to be asking how to hide them. That's what subs are for.
    sub extract { my ($rez) = @_; my @flattened; for my $host ( keys %$rez ) { for my $tgt ( keys %{ $rez->{$host}{tgt} } ) { for my $dev ( keys %{ $rez->{$host}{tgt}{$tgt} } ) { push @rv, [ $tgt, $dev, $rez->{$host}{tgt}{$tgt}{$dev} ]; }}} return @flattened; } for (extract(\%rez)) { my ($tgt, $dev, $val) = @$_; ... }
    Or a callback approach:
    sub walk(&@) { my ($cb, $rez) = @_; for my $host ( keys %$rez ) { for my $tgt ( keys %{ $rez->{$host}{tgt} } ) { for my $dev ( keys %{ $rez->{$host}{tgt}{$tgt} } ) { $cb->($tgt, $dev, $rez->{$host}{tgt}{$tgt}{$dev}); }}} } walk { my ($tgt, $dev, $val) = @_; ... } \%rez;
Re: A better way for walking through HoHoH ?
by stonecolddevin (Vicar) on Mar 24, 2010 at 19:15 UTC
Re: A better way for walking through HoHoH ?
by metaperl (Curate) on Mar 24, 2010 at 16:08 UTC

      Data::Diver is only useful if you know the path to the node, but that's not the case here.

      The latter looks interesting, but only if it can be convinced to returns what "*" matched along with the found node (and a quick scan didn't reveal any indication that it can).

Re: A better way for walking through HoHoH ?
by doug (Pilgrim) on Mar 24, 2010 at 16:47 UTC

    The only other obvious way (to me, at least) would be to flatten this into a list of [$host, $tgt, $dev, $X] tuples. HoHoH or LoL is entirely up to you.

    BTW: You never state what the content of  $rez{$host}->{$tgt}->{$dev} is, so I called it $X. Anyhow, it will need to go along for the ride.

    - doug

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://830512]
Approved by Corion
Front-paged by Arunbear
and a soft breeze sighs...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (4)
As of 2018-05-24 03:02 GMT
Find Nodes?
    Voting Booth?