nurp nurp - 8 months ago 96
Perl Question

Compare values of two hashes with mixed types

I want to compare the hash if the key-value pairs are same in the second hash. I don't want to use smartmatch as it gives warnings.

What is the best way to compare two hashes with integers,strings and maybe also arrays in it?

use warnings;
use diagnostics;

my $hash1={"key_str"=>"a string", "key_int"=>4};
my $hash2={"key_str"=>"b string", "key_int"=>2};

foreach my $key ( keys(%$hash1) ) {
if ($hash1->{$key} != $hash2->{$key}) {
print($key);
}
}


the output as expected is:

Argument "b string" isn't numeric in numeric ne (!=) at hash_compare.pl line 8 (#1)
(W numeric) The indicated string was fed as an argument to an operator
that expected a numeric value instead. If you're fortunate the message
will identify which operator was so unfortunate.

Argument "a string" isn't numeric in numeric ne (!=) at hash_compare.pl line 8 (#1)

Answer Source

First, Perl does not have types. It does not distinguish between strings and numbers (on the outside).

Furthermore, it does not make a difference between numbers and strings on this level. The numerical context and string context matters if you check what's greater or less than. Consider this:

my $foo = 200;
my $bar = 99;
print $foo > $bar ? $foo : $bar;

Obviously it will print 200, because 200 is numerically larger than 99.

my $foo = 200;
my $bar = 99;
print $foo gt $bar ? $foo : $bar;

But this will print 99, because 9 is alphanumerically (as in string) greater than 2. It compared the numbers of the code points for the characters.

But if all you want to do is check for inequality, the ne operator is fine. Even when you are not sure whether there are things other than numbers in your input.

foreach my $key ( keys(%$hash1) ) {
    if ($hash1->{$key} ne $hash2->{$key}) {
        print($key);
    }
}

eq (and ne) are smart enough to see if a number was initially a string or a number without quotes, because the internal representation of those differs.

Warning, technical details ahead.

Scalar values are saved in _SV_s. These in terms can contain different things. There's a special internal type for simple integers called IV, and also one called PV for strings. Perl internally converts between those two as needed when you use numbers inside of strings, or vise versa.

You can get some debugging information about the internal representation of data with Dump from Devel::Peek.

use Devel::Peek;

Dump("01");
Dump(01);

This will output:

SV = PV(0x19560d0) at 0x19327d0
  REFCNT = 1
  FLAGS = (POK,READONLY,IsCOW,pPOK)
  PV = 0x1c94fd0 "01"\0
  CUR = 2
  LEN = 10
  COW_REFCNT = 0
SV = IV(0x19739b0) at 0x19739c0
  REFCNT = 1
  FLAGS = (IOK,READONLY,pIOK)
  IV = 1

As you can see, the first one is a string, and the second one is a number. But if we do this

print "01" eq 01;

there is no output, because the 01 is an integer and will be converted to "1" for comparison. Since the 0 of "01" is not equal to 1, nothing gets printed.


If the values of your data structures are more complex, you need to walk the structure. Each type of element needs to have its own handling. There could be array references, hash references, scalar references, scalars, glob references, dualvars and so on. There might be objects that you want to treat specially.

I suggest taking a look at how Test::Deep implements this. If you decide to use it in production code (and not a unit test), you can use Test::Deep::NoTest.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download