How do I compare two hashes in Perl without using Data::Compare?
Asked Answered
W

7

19

How do I compare two hashes in Perl without using Data::Compare?

Way answered 13/8, 2009 at 17:59 Comment(3)
Well, you look in Data::Compare and see what they do. Why don't you want to use that module?Inhibitor
Possible duplicate of Perl - Compare two nested hashHydantoin
One reason not to use Data::Compare is that Data::Compare brings in File::Find::Rule, Number::Compare and Text::Glob. I'm trying to simplify things that get loaded in my web app, and that's what brought me here.Marchesa
T
25

The best approach differs according to your purposes. The FAQ item mentioned by Sinan is a good resource: How do I test whether two arrays or hashes are equal?. During development and debugging (and of course when writing unit tests) I have found Test::More to be useful when comparing arrays, hashes, and complex data structures. A simple example:

use strict;
use warnings;

my %some_data = (
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309',
);

my %other_data = (
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309x',
);

use Test::More tests => 1;
is_deeply(\%other_data, \%some_data, 'data structures should be the same');

Output:

1..1
not ok 1 - data structures should be the same
#   Failed test 'data structures should be the same'
#   at _x.pl line 19.
#     Structures begin differing at:
#          $got->{j} = '867-5309x'
#     $expected->{j} = '867-5309'
# Looks like you failed 1 test of 1.
Thetes answered 13/8, 2009 at 18:20 Comment(3)
It looks like Test::Deep was inspired by is_deeply. My question is how do I make cmp_deeply part of a test instead of a test on its own? Because my list of tests only states 8, but everytime I use cmp_deeply, it counts as a test, making my actual number of tests 11 (because I call cmp_deeply 3 times) when I only have 8 functions. I do not want to increase the number of my tests. Is there a more viable solution?Way
@yskhoo. Every time you call one of the testing functions (ok, cmp_deeply, etc.) it counts as a test. As far as I know, there isn't a way to avoid that. If you don't want to commit in advance to a specific number of tests, you can do this when you load the testing module: use Test::More qw(no_plan);.Thetes
You already asked this in #1275256. You did not answer why you cannot increase the number of tests, or what is so complicated about your data structure that you need to call cmp_deeply three times. Please step back from the questions you are asking and determine what the real problem is. If you would provide more information, maybe we can help.Jillene
B
14

Compare is not a detailed enough phrase when talking about hashes. There are many ways to compare hashes:

Do they have the same number of keys?

if (%a == %b) {
    print "they have the same number of keys\n";
} else {
    print "they don't have the same number of keys\n";
}

Are the keys the same in both hashes?

if (%a != %b) {
    print "they don't have the same number of keys\n";
} else {
    my %cmp = map { $_ => 1 } keys %a;
    for my $key (keys %b) {
        last unless exists $cmp{$key};
        delete $cmp{$key};
    }
    if (%cmp) {
        print "they don't have the same keys\n";
    } else {
        print "they have the same keys\n";
    }
}

Do they have the same keys and the same values in both hashes?

if (%a != %b) {
    print "they don't have the same number of keys\n";
} else {
    my %cmp = map { $_ => 1 } keys %a;
    for my $key (keys %b) {
        last unless exists $cmp{$key};
        last unless $a{$key} eq $b{$key};
        delete $cmp{$key};
    }
    if (%cmp) {
        print "they don't have the same keys or values\n";
    } else {
        print "they have the same keys or values\n";
    }
}

Are they isomorphic (I will leave this one up to the reader as I don't particularly want to try implementing it from scratch)?

Or some other measure of equal?

And, of course, this code only deals with simple hashes. Adding complex data structures makes it even more complex.

Breadbasket answered 13/8, 2009 at 18:39 Comment(0)
H
4

See How do I test whether two arrays or hashes are equal?

Perl's FAQ and answers are part of you Perl distribution. You can view the version of this answer that came with your perl by running:

$ perldoc -q equal

in your terminal.

Holocrine answered 13/8, 2009 at 18:1 Comment(0)
C
4

Test::Deep::NoTest has the same functionality.

Convery answered 17/4, 2015 at 1:33 Comment(0)
A
2

Quick, dirty, and I'm sure not that efficient:

use strict;
use warnings;

use Data::Dumper;

sub compare ($$) {
    local $Data::Dumper::Terse  = 1;
    local $Data::Dumper::Indent = 0;
    Dumper(shift) eq Dumper(shift);
}

my %a = ( foo => 'bar', bar => [ 0 .. 3 ] );
my %b = ( foo => 'bar', bar => [ 0 .. 3 ] );
my %c = ( foo => 'bar', bar => [ 0 .. 4 ] );

print Dumper compare \%a, \%b;
print Dumper compare \%a, \%c;
Askja answered 13/8, 2009 at 18:58 Comment(5)
This approach plus Text::Diff prints a useful report.Enterprise
Also you should do local $Data::Dumper::Sortkeys = 1; to ensure same order of the keys.Jenifferjenilee
@skaurus: Why? Wouldn't they be in the same order?Askja
Does not work. I using Perl version 5.10. This is what is displayed: $VAR1 = ''; $VAR1 = '';Sugarplum
@zakovyrya: Since perl 5.17.6 hashes have random key order. See perldoc.perl.org/perlsec.html#Algorithmic-Complexity-Attacks for details.Lippi
L
-1

For comparing:

sub HashCompare {
  my ( $a, $b ) = @_;
  my %rhash_1 = %$a;
  my %rhash_2 = %$b;

  my $key         = undef;
  my $hash_2_line = undef;
  my $hash_1_line = undef;

  foreach $key ( keys(%rhash_2) ) {
   if ( exists( $rhash_1{$key} ) ) {
    if ( $rhash_1{$key} ne $rhash_2{$key} ) {
     print "key $key in $file_1 = $rhash_1{$key} & $rhash_2{$key} in $file_2\n";
         }
       }
     }
     else {
        print "key $key in  $file_1 is not present in $file_2\n";

            #next;
        }
    }

    foreach my $comp_key ( keys %rhash_1 ) {
        if ( !exists( $rhash_2{$comp_key} ) ) {
            print MYFILE "key $comp_key in  $file_2 is not present in $file_1\n";
        }
    }
    return;
}

Creating hash with no duplicate keys:

sub CreateHash {
    my (@key_val_file ) = @_;
    my $key_count      = 1;
    my %hash_key_val   = ();
    my $str4           = undef;

    local $/ = undef;

    foreach my $each_line (@key_val_file) {
            @key_val = split( /,/, $each_line );
            if ( exists( $hash_key_val{$key_val[0]} ) ) {
                    $key_count = $key_count + 1;
                    $str4      = $key_val[0] . " occurence-" . $key_count;
                    $hash_key_val{$str4} = $key_val[1];
                }
                else {
                    $hash_key_val{$key_name} = $key_val[1];
                }
            }
        }

        $key_count = 1;

    close FILE;

    return %hash_key_val;
}
Life answered 23/4, 2012 at 17:23 Comment(2)
please provide an explanation for your answer.Cahra
where did $key_name come from?Telemann
O
-1
use strict;
use warnings;
use JSON::XS;

my $some_data ={
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309',
};

my $other_data = {
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309x',
};

use Test::More tests => 1;
is_deeply(JSON::XS->new->canonical( 1 )->utf8->encode($other_data), JSON::XS->new->canonical( 1 )->utf8->encode($some_data), 'data structures should be the same');

Example

#   Failed test 'data structures should be the same'
#   at ./t/a.t line 18.
#          got: '{"a":[1,2,"x"],"b":{"biz":"buz","foo":"bar"},"j":"867-5309x"}'
#     expected: '{"a":[1,2,"x"],"b":{"biz":"buz","foo":"bar"},"j":"867-5309"}'
# Looks like you failed 1 test of 1.

use strict;
use warnings;
use JSON::XS;

my $some_data ={
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309',
};

my $other_data = {
    a => [1, 2, 'x'],
    b => { foo => 'bar', biz => 'buz' },
    j => '867-5309',
};

use Test::More tests => 1;
is_deeply(JSON::XS->new->canonical( 1 )->utf8->encode($other_data), JSON::XS->new->canonical( 1 )->utf8->encode($some_data), 'data structures should be the same');

Example2

All tests successful.
Oliveolivegreen answered 11/10, 2023 at 22:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.