Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

A better way for walking through HoHoH ?

by ZlR (Chaplain)
on Mar 24, 2010 at 10:14 UTC ( [id://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

    ZlR:

    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.

    ...roboticus

      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.

    Mike
      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:
        DBI
        SQL::Statement
        Text::CSV_XS
        DBD::CSV

        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 (Patriarch) 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 (Parson) 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?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (2)
As of 2025-02-08 04:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Which URL do you most often use to access this site?












    Results (95 votes). Check out past polls.