将两个哈希值与混合类型进行比较

时间:2017-11-14 10:58:45

标签: perl hash

如果第二个哈希中的键值对相同​​,我想比较哈希值。我不想使用smartmatch,因为它会发出警告。

将两个哈希与整数,字符串以及可能还有数组进行比较的最佳方法是什么?

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);
    }
}

预期的输出是:

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)

3 个答案:

答案 0 :(得分:8)

首先,Perl没有类型。它不区分字符串和数字(​​在外面)。

此外,它在这个级别上的数字和字符串之间没有区别。 数字上下文字符串上下文对于检查大于或小于的内容很重要。考虑一下:

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

显然它会打印200,因为200在数值上大于99。

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

但是这会打印99,因为9是字母数字(如字符串中)大于2。它比较了字符的代码点数。

但如果你要做的只是检查不平等,那么ne运算符就可以了。即使您不确定输入中是否还有数字以外的东西。

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

eq(和ne)非常聪明,可以查看数字最初是字符串还是没有引号的数字,因为这些数字的内部表示不同。

警告,技术细节未来。

标量值保存在_SV_s中。这些术语可以包含不同的东西。对于称为 IV 的简单整数,还有一个特殊的内部类型,对于字符串,还有一个名为 PV 的内部类型。当你在字符串中使用数字时,Perl会根据需要在这两者之间进行内部转换,反之亦然。

您可以使用Devel::Peek中的Dump获取有关数据内部表示的一些调试信息。

use Devel::Peek;

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

这将输出:

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

如您所见,第一个是字符串,第二个是数字。 但是,如果我们这样做

print "01" eq 01;

没有输出,因为01是一个整数,并且会转换为"1"进行比较。由于0的{​​{1}}不等于"01",因此不会打印任何内容。

如果数据结构的值更复杂,则需要遍历结构。每种类型的元素都需要有自己的处理。可能有数组引用,哈希引用,标量引用,标量,glob引用,dualvars等。可能有一些你想特别对待的物品。

我建议看看Test::Deep如何实现这一点。如果您决定在生产代码中使用它(而不是单元测试),则可以使用Test::Deep::NoTest

答案 1 :(得分:2)

您可以use Scalar::Util qw( looks_like_number );确定该值是数字还是字符串。 Scalar::Util是Perl附带的标准模块。有关标准模块的列表,请参阅perldoc perlmodlib

#!/usr/bin/env perl

# always use these two
use strict;
use warnings;

# handle errors in open and close
use autodie; # See http://perldoc.perl.org/autodie.html

use Scalar::Util  qw( looks_like_number );

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

foreach my $key ( keys(%$hash1) ) {
    if( looks_like_number( $hash1->{$key} ) && looks_like_number( $hash2->{$key} ) ){
        if ($hash1->{$key} != $hash2->{$key}) {
            print "number value of $key is different\n";
        }
    }else{
        if ($hash1->{$key} ne $hash2->{$key}) {
            print "string value of $key is different\n";
        }
    }
}

答案 2 :(得分:1)

我编写了一个不使用任何模块的程序。在很多情况下,我已在下面的程序中测试过,工作正常,但如果您发现任何失败的情况,请告诉我。

如果您不确定要比较的数据类型,请始终使用ne进行比较。 !=仅适用于整数,ne适用于整数和字符串。

use strict;
use warnings;

use feature 'say';

my $hash1 = {
    'key1' => 'value1',
    'key2' => [1, 2, 2],
    'key3' => {1=>1, 2=> [5, 7]},
};

my $hash2 = {
    'key1' => 'value1',
    'key2' => [1, 2, 2],
    'key3' => {1=>1, 2=> [5, 7]},
};

my $is_same = 0;
$is_same = compare($hash1, $hash2);

if ($is_same) {
    say "Same";
} else {
    say "Not same";
}

sub compare {
    my ($value1, $value2) = @_;
    my $is_same = 1;

    if (ref($value1) eq "ARRAY") {
        if (is_same_sized_array($value1, $value2)) {
            foreach (my $i = 0; $i < @$value1; $i++) {
                if (ref $value1->[$i] eq ref $value2->[$i]) {
                    $is_same = compare($value1->[$i], $value2->[$i]);
                    return 0 unless $is_same;
                } else {
                    return 0;
                }
            }
        } else {
            return 0;
        }
    } elsif (ref($value1) eq "HASH") {
        if (is_same_sized_array([keys %$value1], [keys %$value2])) {
            foreach my $key (sort keys %$value1) {
                if (exists $value2->{$key} && ref $value1->{$key} eq ref $value2->{$key}) {
                    $is_same = compare($value1->{$key}, $value2->{$key});
                    return 0 unless $is_same;
                } else {
                    return 0;
                }
            }
        } else {
            return 0;
        }
    } else {
        if ($value1 ne $value2) {
            return 0;
        }
    }
    return $is_same;
}

sub is_same_sized_array {
    my ($arr1, $arr2) = @_;
    return (@$arr1 == @$arr2) || 0;
}