Perl — сравнение двух вложенных хэшей

Это мой сценарий, где есть 2 хэша, которые были декодированы из 2 файлов JSON.

У меня есть 2 сложных хэша,

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

Я хочу сравнить эти 2 хеша на равенство и использовать Compare of Data::Compare и is_deeply Test::More. Оба не игнорируют порядок массива.
Я хочу сравнить, игнорируя порядок значений массива ключа 'k21'.
Мое приложение заполняет массив из 'keys %hash', что дает случайный порядок.
> Пробовал игнорировать 'ignore_hash_keys' Data::Compare, но мой хэш иногда может быть сложным, и я не хочу его игнорировать.

Ключ «k21» также иногда может иметь массив хэшей.

$hash3 = {k1=> { k11 => v1}, k2 => { k21 => [{v3 => v31}, {v2 => v22}] }}

Как мне сравнить такой сложный хеш, игнорируя порядок массива.


person Girish    schedule 10.05.2016    source источник


Ответы (1)


Вы можете использовать Test::Deep, который предоставляет cmp_deeply. Он намного более универсален, чем is_deeply Test::More.

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 отлично подходит для временного переопределения вещей, вместо того, чтобы возиться с typeglobs.

По сути, все, что нам нужно сделать, это заменить вызов Test::Deep::arrayelementsonly в последней строке Test::Deep::Array::descend вызовом bag(). Остальное просто копируется (отступ мой). Для небольших monkey-patch копия существующего кода с небольшой модификацией обычно является самым простым подходом. .

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.

person simbabque    schedule 10.05.2016
comment
Спасибо simbabque за ответ, как мне динамически упаковать все ссылки на массив внутри хеша. $hash2 выводится и содержит вложенный хеш, как мне динамически сказать cmp_deeply, что любое сравнение массивов должно выполняться через мешок, или я должен пройти по хэшу и выполнить сравнение отдельных пакетов. - person Girish; 11.05.2016
comment
Спасибо @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"); Вышеупомянутый stmt работает, но хочу, чтобы нижний работал, выполняя сравнения сумок. cmp_deeply($arr_of_h1, $arr_of_h2,"Hash are equal"); - person Girish; 11.05.2016
comment
@user Я думаю, вам придется это построить. Или может быть есть какой-то зацепляющий механизм. Вы читали полную документацию Test::Deep? В противном случае вы можете использовать metacpan.org/pod/Data::Visitor или что-то подобное для сборки обход. Я предлагаю вам задать новый вопрос для этого, так как он отличается от исходного вопроса здесь. - person simbabque; 11.05.2016
comment
@user смотрите мое обновление. На самом деле, у меня недавно была такая же проблема в тесте, но я решил изменить тестовый пример. Разобраться с этим было весело. - person simbabque; 11.05.2016
comment
Спасибо за удивительный ответ. С переопределением я смог сравнить 2 сложных хэша, игнорируя порядок массива. - person Girish; 13.05.2016
comment
@Girish рад, что смог помочь. Если мой ответ ответил на ваш вопрос, пожалуйста, примите его, нажав на галочку слева под голосами. Вы можете пройти тур, если вам нужна помощь. :) - person simbabque; 13.05.2016