Perl - 比较两个嵌套哈希表

4
这是我的情景,其中有两个哈希值已从两个JSON文件中解码出来。
我有两个复杂的哈希值,
$hash1 = {k1=> { k11 => v1, k12 => v2}, k2 => { k21 => [v1, v2, v3] }}
$hash2 = {k1=> { k11 => v1, k12 => v2}, k2 => { k21 => [v3, v2, v1] }}

我想比较这两个哈希是否相等,并使用Data::Compare的Compare和Test::More的is_deeply。但是,两者都不会忽略数组的顺序。 我想要比较键“k21”的数组值时忽略其顺序。 我的应用程序从“keys%hash”中填充数组,该方法会随机排序。 尝试使用Data::Compare的“ignore_hash_keys”,但我的哈希有时可能很复杂,不想忽略它们。 键“k21”有时也可能有哈希数组。
$hash3 = {k1=> { k11 => v1}, k2 => { k21 => [{v3 => v31}, {v2 => v22}] }}

如何忽略数组顺序比较复杂的哈希值。

1个回答

7
你可以使用Test::Deep来提供cmp_deeply,它比Test::More的is_deeply更加通用。
use Test::Deep;

my $hash1 = {
    k1 => { k11 => 'v1', k12 => 'v2' }, k2 => { k21 => [ 'v1', 'v2', 'v3' ] } };
my $hash2 = {
    k1 => { k11 => 'v1', k12 => 'v2' }, k2 => { k21 => bag( 'v3', 'v2', 'v1' ) } };

cmp_deeply( $hash1, $hash2, );

这个技巧的关键是bag()函数,它忽略了元素的顺序。
这是一个包比较,也就是说,它比较两个数组但忽略元素的顺序。
更新: 来自你的评论:
如何在哈希内动态地打包所有数组引用
对Test::Deep代码进行一些挖掘,发现可以覆盖它。首先看一下Test::Deep本身,找到一个Test::Deep ::Array,它处理数组。所有处理T::D中的内容的包都有一个descend方法。所以这就是我们需要钩入的地方。
Sub::Override非常适合暂时覆盖东西,而不是搞乱类型。
基本上,我们需要做的就是将Test::Deep::Array::descend中对Test::Deep::arrayelementsonly的调用替换为对bag()的调用。其余部分只需复制(缩进是我的)。对于小的猴子补丁,将现有代码的副本稍作修改通常是最简单的方法。
use Test::Deep;
use Test::Deep::Array;
use Sub::Override;

my $sub = Sub::Override->new(
    'Test::Deep::Array::descend' => sub {
        my $self = shift;
        my $got  = shift;

        my $exp = $self->{val};

        return 0 unless Test::Deep::descend( 
             $got, Test::Deep::arraylength( scalar @$exp ) );

        return 0 unless $self->test_class($got);

        return Test::Deep::descend( $got, Test::Deep::bag(@$exp) );
    }
);

my $hash1 = {
    k1 => { k11 => 'v1', k12 => 'v2' },
    k2 => { k21 => [ 'v1', 'v2', 'v3' ] }
};
my $hash2 = {
    k1 => { k11 => 'v1', k12 => 'v2' },
    k2 => { k21 => [ 'v3', 'v2', 'v1' ] }
};

cmp_deeply( $hash1, $hash2 );

这将使测试通过。

确保重置覆盖,通过取消定义$sub或让其超出范围,否则如果您的测试套件的其余部分也使用Test::Deep,则可能会遇到一些奇怪的问题。


感谢simbabque的回答, 如何动态地将哈希内所有数组引用打包。$hash2是生成的输出,其中包含嵌套哈希,如何动态地告诉cmp_deeply任何数组比较都应该通过bag进行,或者遍历哈希并进行单个bag比较。 - Girish
感谢@simbabque,我如何动态地将哈希内的所有数组引用打包起来。
my $arr_of_h1 = {'a' => [1, 2 , 3], b => [{2 => 1}, {1 => 1}, {3 => 1}]};
my $arr_of_h2 = {'a' => [1, 2 , 3], b => [{2 => 1}, {3 => 1}, {1 => 1}]}; cmp_deeply($arr_of_h1->{b}, bag(@{$arr_of_h2->{b}}),"Array are equal");
以上语句可以工作,但想要以下语句通过执行bag比较而工作。
cmp_deeply($arr_of_h1, $arr_of_h2,"Hash are equal");
- Girish
@user 我认为你需要构建那个功能。或者也许有一些钩子机制。你有阅读完整的Test::Deep文档吗?否则,你可以使用https://metacpan.org/pod/Data::Visitor或类似的东西来构建遍历。我建议你针对这个问题提出一个新的问题,因为它与此处的初始问题不同。 - simbabque
@user 看看我的更新。事实上,最近在一次测试中我也遇到了同样的问题,但是我决定改变测试用例。解决这个问题很有趣。 - simbabque
1
感谢您提供的精彩答案。通过使用覆盖(override),我能够比较两个复杂哈希(hash)并忽略数组的顺序。 - Girish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接