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

Hello Monks, I am beginner in perl. I want to compare two hashes of hashes(2D Hashes). I want to use for loop because I want to do other operation after comparison. I dont want to use 'is_deeply' or 'eq_or_diff' constructs. Is it possible to do hash comparison using for loop? Can anyone please help?

%hash1 ={ 'aunit' => { '1111' => 12, '1112' => 88, '1113' => 82 }, 'bunit' => { '2111' => 67, '2122' => 97 } 'cunit' => { '3111' => 10, '3112' => 20 } }; %hash2 ={ 'aunit' => { '1111' => 10, '1112' => 88, '1113' => 82, '1114' => 25 }, 'bunit' => { '2111' => 12, '2122' => 97 } 'cunit' => { '3111' => 10, '3112' => 20 } 'dunit' => { '4111' => 5, '4112' => 50 } };

Replies are listed 'Best First'.
Re: Compare two Hashes of Hashes
by kennethk (Abbot) on Dec 16, 2016 at 15:57 UTC

    Can you explain why is_deeply and eq_or_diff won't work? What have you tried, and what didn't work? This sounds like a homework assignment, or at least an XY Problem. We are happy to help debug, but you won't learn if you don't struggle through.

    Some basic discussion of how to run the comparison is a FAQ (How do I test whether two arrays or hashes are equal? in perlfaq4). You'll need to use two nested loops, in contrast to the example there.


    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

      Thank you Kennethk for reply. Sorry I forgot to add what I tried. I made a function to compare first level key and value of two hashes but I am struggling to check for second level values. Can you please help?

      comp (\% hash1, \%hash2); sub comp { my $hash1 = shift; my $hash2 = shift; if( keys %{$hash1} != keys %{$hash2} ) { print "Hash1 has ", scalar(keys %{$hash1}), " units but hash2 has ", scalar(keys %{$hash2}), "\n"; } for my $key_hash1 (sort {lc($a) cmp lc($b) } keys %{$hash1} ) { if( ! exists $hash2->{$key_hash1} ) { print "Hash1 contains unit '$key_hash1' but hash2 has no such un +it\n"; next; } if( $hash1->{$key_hash1} ne $hash2->{$key_hash1} ) { print "Both hashes have '$key_hash1' as a unit ", "but hash1's value is '$hash1->{$key_hash1}' ", "and hash2's value is '$hash2->{$key_hash1}'\n"; } } }
        Thank you for posting that code. Note that with the block
        for my $key_hash1 (sort {lc($a) cmp lc($b) } keys %{$hash1} ) { if( ! exists $hash2->{$key_hash1} ) { print "Hash1 contains unit '$key_hash1' but hash2 has no such un +it\n"; next; }
        you will check if %hash2 is missing keys from %hash1, but you lack code to do the reverse check. So you probably want to add something like:
        for my $key_hash2 (sort {lc($a) cmp lc($b) } keys %{$hash2} ) { if( ! exists $hash1->{$key_hash2} ) { print "Hash1 contains unit '$key_hash2' but hash1 has no such un +it\n"; } }
        For adding another layer of checks, you need to replace your value check
        if( $hash1->{$key_hash1} ne $hash2->{$key_hash1} ) { print "Both hashes have '$key_hash1' as a unit ", "but hash1's value is '$hash1->{$key_hash1}' ", "and hash2's value is '$hash2->{$key_hash1}'\n"; }
        with another loop over the keys of the hashes, very much like you've done already.
        for my $key_hash1 (sort {lc($a) cmp lc($b) } keys %{$hash1} ) { if( ! exists $hash2->{$key_hash1} ) { print "Hash1 contains unit '$key_hash1' but hash2 has no such un +it\n"; next; } for my $key_hash1_tier2 (sort {lc($a) cmp lc($b) } keys %{$hash1-> +{$key_hash1}} ) { if( ! exists $hash2->{$key_hash1}{$key_hash1_tier2} ) { print "Hash1 $key_hash1 contains unit '$key_hash1_tier2' but h +ash2 has no such unit\n"; next; } if( $hash1->{$key_hash1}{$key_hash1_tier2} ne $hash2->{$key_hash +1}{$key_hash1_tier2} ) { print "Both hashes have '$key_hash1'-'$key_hash1_tier2' as a u +nit ", "but hash1's value is '$hash1->{$key_hash1}{$key_hash1_t +ier2}' ", "and hash2's value is '$hash2->{$key_hash1}{$key_hash1_t +ier2}'\n"; } } }
        subject to the same criticism I made before about not checking which elements are in %hash2 but not %hash1. You can see this clearly if you reverse the order of the arguments to comp.

        #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: Compare two Hashes of Hashes
by LanX (Saint) on Dec 16, 2016 at 16:40 UTC
    To give you a starter for your generic question, here a generic answer:

    do something like

    while ( my ($key,$value) = each %$h_ref1 ) { # Test key and value in %$h_ref2 # according to whatever criteria you need. }

    if the $value is a hash_ref again ( tested with ref or reftype from Scalar::Util ) do a second loop.

    (keep in mind that you also need to test for keys present in hash2 which don't show up in the iterated hash1)

    If you need a deeper nesting consider a recursive function where you pass two references.

    (there are plenty of more elaborated examples already in our archives.)

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Compare two Hashes of Hashes
by kcott (Archbishop) on Dec 17, 2016 at 06:55 UTC

    G'day Eshan_k,

    "I am beginner in perl."

    Yes, indeed. I strongly recommend that you read "perlintro -- Perl introduction for beginners". It's a fairly short read (under a dozen screenfuls on my monitor) and will take you through the basics. Perhaps more importantly, every section is peppered with links to more detailed discussions and advanced topics: it's a great reference source when your coding requirements become more complex.

    The test data you posted has serious problems: you're assigning hashrefs to undeclared hashes which will end up having a single key, which looks something like "HASH(0x7fc1c3005498)", and a single undef value. The strict and warnings pragmata (both introduced at the start of perlintro) would have alerted you to this.

    I see you've added additional information (the code you tried and the problem you had) in "Re^2: Compare two Hashes of Hashes". Consider this a reply to them both.

    In the code below,

    • I've abstracted the key comparison into a single function, comp_keys(): you simply pass it two hashrefs and it compares the keys. This eliminates your first-level/second-level problems as it has no knowledge of the source level; in fact, you could use it to compare the keys at any level.
    • The value comparison function, comp_vals(), is much simpler; although, it does expect that comp_keys() has been run first. It's not run for the first-level keys as their values should all be hashrefs (but you might want some validation in production-grade code). It doesn't need to check the number of values: there's one for each key and that's already been checked.
    • The ary2str() function perhaps needs some explanation. This is aiming to join the array of strings using a character not found elsewhere. Consider the problem of joining the elements of two quite different arrays, ('a', 'b-c') and ('a-b', 'c'), with a dash ('-'): both result in the same string, 'a-b-c'. I find $; to be quite handy in this situation: it used to be used to create complex data structures in Perl4 (before we had references, in Perl5) and it's unlikely to occur in any hash values. It's just used a joiner here: if it's unsuitable for your needs, pick something else. You can read more about it in perlvar.

    Here's my test code:

    #!/usr/bin/env perl -l use strict; use warnings; { my @tests = ( { x => { a => 1, b => 2 }, y => { c => 3} }, { x => { a => 1, b => 2 }, z => { c => 3} }, { x => { a => 1, b => 2 }, y => { c => 3}, z => {} }, { x => { a => 1, b => 2 }, y => { c => 3, d => 4 } }, { x => { a => 1, b => 2 }, y => { d => 3} }, { x => { a => 1, d => 2 }, y => { c => 3} }, { x => { a => 1, b => 2 }, y => { c => 4} }, ); for (0 .. $#tests) { print "Comparing \$tests[0] with \$tests[$_]"; comp($tests[0], $tests[$_]); print '-' x 40; } } sub comp { my ($h1, $h2) = @_; return unless comp_keys($h1, $h2); for (sort keys %$h1) { print "Comparing keys in '$_' hashref."; return unless comp_keys($h1->{$_}, $h2->{$_}); print "Comparing values in '$_' hashref."; return unless comp_vals($h1->{$_}, $h2->{$_}); } } sub comp_keys { my ($h1, $h2) = @_; my @keys = ([sort keys %$h1], [sort keys %$h2]); if (@{$keys[0]} == @{$keys[1]}) { print "Same number of keys."; if (ary2str($keys[0]) eq ary2str($keys[1])) { print "Same keys."; } else { print "Different keys."; return 0; } } else { print "Different number of keys."; return 0; } return 1; } sub comp_vals { my ($h1, $h2) = @_; my @keys = sort keys %$h1; if (ary2str([@$h1{@keys}]) eq ary2str([@$h2{@keys}])) { print "Same values"; } else { print "Different values"; return 0; } return 1; } sub ary2str { join $;, @{$_[0]} }

    The output's a tad lengthy (65 lines). It's in the spoiler.

    — Ken

Re: Compare two Hashes of Hashes
by BillKSmith (Monsignor) on Dec 16, 2016 at 19:01 UTC
    There are two ways that a pair of two-dimensional hashes can be 'equal'. Corresponding values in all the inner hashes have equal values or the corresponding values in the outer hashes refer to the same set of inner hashes. (The later of course implies the first) You may want to require (or exclude) the latter.
    Bill