Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

How to check that keys in two hashes match and get the corresponding values

by NewLondonPerl1 (Acolyte)
on Apr 14, 2013 at 20:17 UTC ( [id://1028644]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Perl monks I was hoping you might be able to advise me on how best to do the following. I have been trying for ages but I just cant seem to get it working. I have a large complex data structure that is a hash of hashes of hashes. I am able to parse this but I am struggling with matching up some of key values. Below is a small sample of the data structure at this level that I am trying to parse. As you will see in some of the hashes %filer_device is hash, some of them %filer_volume is a hash, some of them neither is a hash and some of them both are hashes.

"bigstorderv_mpt" => { "%export_name" => "/bb/bigstor/derv", "%filer_device" => "nydevnfs_derv", "%filer_volume" => "/vol/derv" }, "bigstormtg_mpt" => { "%export_name" => "/bb/bigstor/mtgmodel", "%filer_device" => { "\@ridge" => "njdevnfs_mtge", "\@west" => "nydevnfs_mtge" }, "%filer_volume" => "/vol/mtge" }, "build10_mpt" => { "%export_name" => "/bb/source", "%filer_device" => { "\@ridge" => "rnap7751-s", "\@west" => "nydevnfs_sunbbsource" }, "%filer_volume" => { "\@ridge" => "/vol/sunbbsource_c", "\@west" => "/vol/sunbbsource" } },

I have written this subroutine to try and deal with these nested hashes

while (my( $kTag, $vTag ) = each %{$nfshashofhashes} ) { next if ( ref $vTag ne 'HASH' ); print "TAG > $kTag\n"; my $default = delete $vTag->{'%default'}; while (my( $kNFSMNT, $vNFSMNT ) = each %{$vTag} ) { next if ($kNFSMNT =~ m/optswt_nfs\b/); #print "...$kTag $kNFSMNT\n"; my $default = delete $vNFSMNT->{'default'} // delete $ +vNFSMNT->{'%default'}; while (my($kDEFAULT, $vDEFAULT) = each %{$default}) { + #Get all the defaults key/value pairs for each nfs hash #print "$kNFSMNT:$kDEFAULT $vDEFAULT\n"; $MNTOPTS = "$vDEFAULT" if ($kDEFAULT eq '%mount_op +ts'); $MNTUSER = "$vDEFAULT" if ($kDEFAULT eq '%mount_us +er'); $MNTGRP = "$vDEFAULT" if ($kDEFAULT eq '%mount_gro +up'); $MNTACL = "$vDEFAULT" if ($kDEFAULT eq '%mount_acl +'); } while (my( $kNFSTOKEN, $vNFSTOKEN ) = each %{$vNFSMNT} + ) { next if ( $kNFSTOKEN =~ m/\%comment\b/ ); #print "*** $kTag $kNFSTOKEN $vNFSTOKEN\n"; #Prin +ts @dev home7_mpt, @dev home-lnk-mpt etc and value which is a hash co +ntaing %export_name, %filer_device etc $FILERDEVICE = $vNFSTOKEN->{'%filer_device'}; $FILERDEVICEHASH = ref $FILERDEVICE ? $FILERDEVICE + : { '' => $FILERDEVICE }; $FILERVOLUME = $vNFSTOKEN->{'%filer_volume'}; $FILERVOLUMEHASH = ref $FILERVOLUME ? $FILERVOLUME + : { '' => $FILERVOLUME }; $EXPORTFS = $vNFSTOKEN->{'%export_name'}; #print "$kTag $kNFSTOKEN $FILERDEVICE $FILERVOLUME + $EXPORTFS\n"; ###Process %filer_device and %filer_volume. Someti +mes %filer_device is a hash. Sometimes %filer_volume is a hash. Somet +imes both are hashes. Sometimes neither are hashes :-( # my ($FDEVICE, $WESTFDEVICE, $RIDGEFDEVICE, $VOLUME +, $WESTVOL, $RIDGEVOL); for my $DataCenterFD (keys %{$FILERDEVICEHASH}) { $FDEVICE = $FILERDEVICEHASH->{$DataCenterFD} / +/ $FILERDEVICEHASH->{''}; #print "$DataCenterFD\n"; for my $DataCenterVD (keys %{$FILERVOLUMEHASH} +) { $VOLUME = $FILERVOLUMEHASH->{$DataCenterVD +} // $FILERVOLUMEHASH->{''}; #print "$DataCenterVD\n"; ##NEARLY WORKING. JUST NEED TO MATCH VOLUM +E VALUES TO EITHER A @WEST/@RIDGE FILERDEVICE @WEST/@RIDGE #This prints out a line in this format: @d +ev bigstormtg_mpt @ridge /bb/bigstor/mtgmodel /vol/mtge print "$kTag $kNFSTOKEN $FDEVICE $DataCent +erFD $EXPORTFS $VOLUME\n"; } } } } } }

The problem I am having is that the filer_device value doesn't match up with what the filer_volume is defined as. So for instance if there is a hash %filer_device with keys @west and @ridge then the filer_device key @west needs to be mapped to the filer_volume @west key value and the same for the @ridge values if these are defined.

Replies are listed 'Best First'.
Re: How to check that keys in two hashes match and get the corresponding values
by roboticus (Chancellor) on Apr 15, 2013 at 01:49 UTC

    NewLondonPerl:

    I'd suggest rethinking your data structure a bit. The reason you're having a hard time with it is that the data structure isn't regular, so your code is obfuscated by all sorts of special case handling. I couldn't tell what you're wanting to do by looking at your code.

    Looking at the data, your code and the desired output gave me enough to go on, though. At first, I tried to clean up your code to get it going, but it was just too different from how I would've done it, that I kept getting confused. So I just started from scratch and came up with this:

    $ cat 1028644.pl #!/usr/bin/perl use strict; use warnings; my $nfshashofhashes = { "bigstorderv_mpt" => { "%export_name" => "/bb/bigstor/derv", "%filer_device" => "nydevnfs_derv", "%filer_volume" => "/vol/derv", }, "bigstormtg_mpt" => { "%export_name" => "/bb/bigstor/mtgmodel", "%filer_device" => { "\@ridge" => "njdevnfs_mtge", "\@west" => "nydevnfs_mtge" }, "%filer_volume" => "/vol/mtge", }, "build10_mpt" => { "%export_name" => "/bb/source", "%filer_device" => { "\@ridge" => "rnap7751-s", "\@west" => "nydevnfs_sunbbsource" }, "%filer_volume" => { "\@ridge" => "/vol/sunbbsource_c", "\@west" => "/vol/sunbbsource" } }, "build11_mpt" => { "%export_name" => "/bb/source", "%filer_device" => { "fridge" => "rnap7751-s", "\@west" => "nydevnfs_sunbbsource", }, "%filer_volume" => { "\@ridge" => "/vol/sunbbsource_c", "\@west" => "/vol/sunbbsource", "qwest" => "/vol/sunquirks", } }, }; while ( my ($MPT, $rMPT) = each %{$nfshashofhashes} ) { die "MPT<$MPT> NOT A HASH!\n" unless ref $rMPT eq 'HASH'; die "MPT<$MPT> WRONG STRUCTURE!\n" unless exists $rMPT->{'%export_ +name'}; my $EXP = $rMPT->{'%export_name'}; # Set default value and hash ref for devices my ($rFILDEV, $DEVDFLT, $t); $t = $rMPT->{'%filer_device'}; if (ref $t eq 'HASH') { $rFILDEV = $t; $DEVDFLT = '????'; } else { $rFILDEV = { }; $DEVDFLT = $t; } # Same for volumes my ($rFILVOL, $VOLDFLT); $t = $rMPT->{'%filer_volume'}; if (ref $t eq 'HASH') { $rFILVOL = $t; $VOLDFLT = '????'; } else { $rFILVOL = { }; $VOLDFLT = $t; } # Match up the VOL and DEV entries in the hash. Missing values fo +r # DEV and VOL will be filled in with the DFLT values as needed. my %MATCHUP; $MATCHUP{$_}{VOL} = $rFILVOL->{$_} for keys %$rFILVOL; $MATCHUP{$_}{DEV} = $rFILDEV->{$_} for keys %$rFILDEV; # If both were scalars, MATCHUP is empty, so make a single matchin +g record $MATCHUP{'BAR'} = { DEV=>$DEVDFLT, VOL=>$VOLDFLT } if ! keys %MATC +HUP; # And our final results... for (keys %MATCHUP) { my $DEV = $MATCHUP{$_}{DEV} // $DEVDFLT; my $VOL = $MATCHUP{$_}{VOL} // $VOLDFLT; printf "%-20.20s %-20.20s %-20.20s %-20.20s\n", $MPT, $DEV, $VOL, $EXP; } } $ perl 1028644.pl bigstorderv_mpt nydevnfs_derv /vol/derv /bb/big +stor/derv build11_mpt ???? /vol/sunbbsource_c /bb/sou +rce build11_mpt rnap7751-s ???? /bb/sou +rce build11_mpt ???? /vol/sunquirks /bb/sou +rce build11_mpt nydevnfs_sunbbsource /vol/sunbbsource /bb/sou +rce build10_mpt rnap7751-s /vol/sunbbsource_c /bb/sou +rce build10_mpt nydevnfs_sunbbsource /vol/sunbbsource /bb/sou +rce bigstormtg_mpt njdevnfs_mtge /vol/mtge /bb/big +stor/mtgmodel bigstormtg_mpt nydevnfs_mtge /vol/mtge /bb/big +stor/mtgmodel

    I think that'll do it for you. You didn't specify what you were expecting when you had two hashes with mismatched keys, so I added "build11_mpt" to demonstrate it. Feel free to change it as you need it.

    Since I had trouble wrapping my head around your code, I fully expect you might have a little difficulty with mine, so feel free to ask questions.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Thanks ever so much for your help Roboticus. I have tried this out and this is now matching up these key values perfectly. I just have one question though. Maybe it because I dont fully understand all of your code but when I run this its seems to print out lines more than once. This seems to happen when either %filer_device is a hash or %filer_volume is a hash or both are hashes. So for example in the following section:

      "rtsys_mpt": { "%export_name": "/bb/rtsys", "%filer_device": { "@west": "nydevnfs_rtsysgit", "@ridge": "njdevnfs_rtsysgit" }, "%filer_volume": "/vol/rtsysgit" }

      When I run your code on this and just purely print out the volume and device values i get the lines duplicated like so:

      njdevnfs_rtsysgit = /vol/rtsysgit nydevnfs_rtsysgit = /vol/rtsysgit njdevnfs_rtsysgit = /vol/rtsysgit nydevnfs_rtsysgit = /vol/rtsysgit

      All that I need is this:

      njdevnfs_rtsysgit = /vol/rtsysgit nydevnfs_rtsysgit = /vol/rtsysgit

      But for some reason its duplicated? I am not too sure why? and in this section where %filer_device and %filer_volume are both hashes it outputs the same lines 3 times:

      "build18_mpt": { "%export_name": "/bb/mobile", "%filer_device": { "@west": "nydevnfs_mob_build", "@ridge": "rnap2113-s" }, "%filer_volume": { "@west": "/vol/mob_build", "@ridge": "/vol/mob_build_c" } },
      nydevnfs_mob_build = /vol/mob_build rnap2113-s = /vol/mob_build_c nydevnfs_mob_build = /vol/mob_build rnap2113-s = /vol/mob_build_c nydevnfs_mob_build = /vol/mob_build rnap2113-s = /vol/mob_build_c

      All i need is the first two lines. The others are duplicates:

      nydevnfs_mob_build = /vol/mob_build rnap2113-s = /vol/mob_build_c

      Do you know why this is happening please?

        NewLondonPerl1:

        I'm not seeing the problem, so I guess you made some change that you haven't mentioned. Without making any changes other than adding your new test case:

        . . . snip . . . my $nfshashofhashes = { "rtsys_mpt" => { '%export_name' => '/bb/rtsys', '%filer_device' => { '@west' => 'nydevnfs_rtsysgit', '@ridge' => 'njdevnfs_rtsysgit', }, '%filer_volume' => '/vol/rtsysgit', }, "bigstorderv_mpt" => { . . . snip . . .

        I get the following, which seems to be what you're wanting:

        $ perl 1028644.pl bigstorderv_mpt nydevnfs_derv /vol/derv /bb/big +stor/derv build11_mpt ???? /vol/sunbbsource_c /bb/sou +rce build11_mpt rnap7751-s ???? /bb/sou +rce build11_mpt ???? /vol/sunquirks /bb/sou +rce build11_mpt nydevnfs_sunbbsource /vol/sunbbsource /bb/sou +rce build10_mpt rnap7751-s /vol/sunbbsource_c /bb/sou +rce build10_mpt nydevnfs_sunbbsource /vol/sunbbsource /bb/sou +rce rtsys_mpt njdevnfs_rtsysgit /vol/rtsysgit /bb/rts +ys rtsys_mpt nydevnfs_rtsysgit /vol/rtsysgit /bb/rts +ys bigstormtg_mpt njdevnfs_mtge /vol/mtge /bb/big +stor/mtgmodel bigstormtg_mpt nydevnfs_mtge /vol/mtge /bb/big +stor/mtgmodel

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

Re: How to check that keys in two hashes match and get the corresponding values
by gnosti (Chaplain) on Apr 15, 2013 at 08:05 UTC
    Speaking as someone who has probably spent 3/4 of his development time or more in debugging, I would suggest not using sigils $%@ in your keys, i.e. use filer_device instead of %filer_device.

    Perhaps I'm just narrow-minded, but it seems confusing, and not my idea of "Perlish".

    I admit this isn't speaking to your problem.

Re: How to check that keys in two hashes match and get the corresponding values
by NewLondonPerl1 (Acolyte) on Apr 14, 2013 at 20:29 UTC

    So essentially using the above small hash examples this is the output I am looking for:

    @dev bigstorderv_mpt nydevnfs_derv /vol/derv /bb/bigstor/derv @dev bigstormtg_mpt njdevnfs_mtge /vol/mtge /bb/bigstor/mtgmodel @dev bigstormtg_mpt nydevnfs_mtge /vol/mtge /bb/bigstor/mtgmodel @dev build10_mpt rnap7751-s /vol/sunbbsource_c /bb/source @dev build10_mpt nydevnfs_sunbbsource /vol/sunbbsource /bb/source

    I am getting very similar output except I cant seem to get the value of the %volume_device to match the value set for the %filer_device so instead of getting the output above I get some lines are written twice as my sub routine doesn't know which volume_device value to map to the filer_device value so it prints the lines twice with the value for the volume device different on each line Please can you help me with what I am doing wrong here?

Re: How to check that keys in two hashes match and get the corresponding values
by aitap (Curate) on Apr 15, 2013 at 17:43 UTC
    while (my ($name, $hash) = each %nfs) { for my $devloc (ref $hash->{'%filer_device'} ? keys %{$hash->{'%fi +ler_device'}} : '%filer_device') { # hack here my $device = (ref $hash->{'%filer_device'} ? $hash->{'%filer_d +evice'} : $hash)->{$devloc}; # and here my $volume = ref $hash->{'%filer_volume'} ? $hash->{'%filer_vo +lume'}{$devloc} : $hash->{'%filer_volume'}; print "\@dev $name $device $volume $hash->{'%export_name'}\n"; } }
    This code uses a small hack providing itself a fake hash and hash key when '%filer_device' value is not a hash reference. The downside of it is lack of data structure checking, so the real solution should be more verbose (and probably use Params::Validate, Data::Validator, Data::Diver or Data::DPath).

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (5)
As of 2024-04-23 18:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found