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 !
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
| [reply] [d/l] |
|
Yep, now i remember :) that's like what jethro said, but with names, and will really make things clearer.
| [reply] |
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) | [reply] [d/l] |
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
| [reply] [d/l] |
|
Ahhh yes ! i forgot that could be done, ie %$ref ! it will clean things up a lot !
| [reply] |
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.
| [reply] [d/l] |
|
| [reply] |
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"? | [reply] |
|
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 !
| [reply] |
|
| [reply] |
|
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.
| [reply] |
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;
| [reply] [d/l] [select] |
Re: A better way for walking through HoHoH ?
by stonecolddevin (Parson) on Mar 24, 2010 at 19:15 UTC
|
| [reply] |
Re: A better way for walking through HoHoH ?
by metaperl (Curate) on Mar 24, 2010 at 16:08 UTC
|
| [reply] |
|
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).
| [reply] |
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
| [reply] [d/l] [select] |
|
|