cooldood3490
cooldood3490

Reputation: 2498

Comparing two Hash of arrays for equality

I want to compare two hash of arrays to see if they are equal. That is, the key values should contain the same elements.

my %h1 = (
    w1 => ['3','1','2'],
    e2 => ['6','2','4'],
    r1 => ['8', '1'],
);

my %h2 = (
    w1 => ['3','1','2'],
    e2 => ['6','2','4'],
    r1 => ['8','1'],
);

 my %h3 = (
    w1 => ['3','1','2'],
    e2 => ['6','2','4'],
    r1 => ['4','1'],
);

my $i = 0;

foreach ( keys %h2 ){
    my $conditional = scalar keys %h2;  # 3
    if ([sort @{$h1{$_}}] ~~ [sort @{$h2{$_}}]) {
        $i++;
    }
    if ($i eq $conditional){
        print "true\n";
    }
}

Comparing %h1 and %h2 should return true since they are equal. Comparing %h1 and %h3 prints nothing. 1) Is there a better way of doing this?

EDIT

I would like to do this without using a function from a module.

Upvotes: 3

Views: 3132

Answers (3)

alexlopashev
alexlopashev

Reputation: 186

I'd create separate subs for each operation: test if arrays are equal and same for hashes:

sub is_arrays_equal($$) {
    my ($a, $b) = @_;
    if (scalar @$a != scalar @$b) {
        return 0;
    }
    for my $i (0 .. $#{$a}) {
        if ($a->[$i] ne $b->[$i]) {
            return 0;
        }
    }
    return 1;
}

sub is_hoa_equal($$) {
    my ($a, $b) = @_;
    if (scalar(keys %$a) != scalar(keys %$b)) {
        return 0;
    }
    for my $k (keys %$a) {
        if (exists $b->{$k}) {
            if (ref $a->{$k} ne 'ARRAY' || ref $b->{$k} ne 'ARRAY' || !is_arrays_equal($a->{$k}, $b->{$k})) {
                return 0;
            }
        }
        else {
            return 0;
        }
    }
    return 1;
}

Note: change 'ne' to '!=' or add logics to compare your scalar values.

Your solution is not good because of sorting each array and other heavy data processing. If your need equality test for all types of hashes (not only for HoA as in my solution) just add scalar test and hash test, which will be recursive invocation of main sub, something like that:

sub arrays_equal {
    my ( $a, $b ) = @_;
    if ( scalar @$a != scalar @$b ) {
        return 0;
    }
    for my $i ( 0 .. $#{$a} ) {
        my $va = $a->[$i];
        my $vb = $b->[$i];
        if ( ref $va ne ref $vb ) {
            return 0;
        }
        elsif ( ref $va eq 'SCALAR' && $va ne $vb ) {
            return 0;
        }
        elsif ( ref $va eq 'ARRAY' && !arrays_equal( $va, $vb ) ) {
            return 0;
        }
        elsif ( ref $va eq 'HASH' && !hashes_equal( $va, $vb ) ) {
            return 0;
        }
    }
    return 1;
}

sub hashes_equal {
    my ( $a, $b ) = @_;
    if ( scalar( keys %$a ) != scalar( keys %$b ) ) {
        return 0;
    }
    for my $k ( keys %$a ) {
        if ( exists $b->{$k} ) {
            my $va = $a->{$k};
            my $vb = $b->{$k};
            if ( ref $va ne ref $vb ) {
                return 0;
            }
            elsif ( ref $va eq 'SCALAR' && $va ne $vb ) {
                return 0;
            }
            elsif ( ref $va eq 'ARRAY' && !arrays_equal( $va, $vb ) ) {
                return 0;
            }
            elsif ( ref $va eq 'HASH' && !hashes_equal( $va, $vb ) ) {
                return 0;
            }
        }
        else {
            return 0;
        }
    }
    return 1;
}

This code checks scalars as strings. You can add is_number or smth like that or just change 'ne' to '!=' (but beware of warnings if there will be not only numbers).

Upvotes: 1

mob
mob

Reputation: 118605

Data::Dumper is a core module that serializes data structures. Then you can test the serializations for string equality.

use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
if (Dumper(\%hash1) eq Dumper(\%hash2)) {
    print "hashes are equal\n";
}

If even Data::Dumper is off-limits, write your own serialization routine that converts an HoA to a string, and compare the strings. Just make sure you don't get tripped up by the randomish order that hash keys are returned in (that's what the $Data::Dumper::Sortkeys=1 line is for in the example above).

Upvotes: 3

frezik
frezik

Reputation: 2316

Try Test::Deep::NoTest, which handles everything Test::Deep does, except it just returns true or false without outputing a TAP test.

print "true" if eq_deeply(\%h1, \%h2)

Upvotes: 4

Related Questions