Sebastian Sulger
Sebastian Sulger

Reputation: 101

Compare values of keys across multiple hashes in Perl

I have a set of hashes (with preserved key sequence using Tie::IxHash), and I'd like to compare their values for each key. The number of hashes can vary. For example, I'd like to count how many times the key "1" is assigned the values "1" and "0". I know there should be a nice fast way to count the value matches if I put the hashes in an array of hashes and then loop through them, but I'm stuck and can't figure it out on my own.

%hash1 = ( 1 => '1',
           2 => '1',
           3 => '0',
           4 => '0',
         );

%hash2 = ( 1 => '1',
           2 => '1',
           3 => '1',
           4 => '0',
         );

%hash3 = ( 1 => '1',
           2 => '1',
           3 => '0',
           4 => '1',
         );

%hash4 = ( 1 => '1',
           2 => '0',
           3 => '0',
           4 => '1',
         );

The intended result for the above is:

$key1: $agr1 = 4; $agr0 = 0;
$key2: $agr1 = 3; $agr0 = 1;
$key3: $agr1 = 1; $agr0 = 3;
$key4: $agr1 = 2; $agr0 = 2;

Right now what I ended up doing is looping through the keys of the first hash and then subsequently comparing them to each of the other hashes, which is tedious and stupid for obvious reasons.

Thanks for your help!

Correction: I'm using hashes, not hash-refs. Edited the above code accordingly.

Upvotes: 0

Views: 1247

Answers (3)

tangent
tangent

Reputation: 561

This allows a bit of flexibility in your result as you can put any desired values in @wanted. Assumes your hashes are actually hash references (ambiguous in your sample):

my @hashes = ($hash1,$hash2,$hash3,$hash4);
my %count;
for my $hash (@hashes) {
    $count{ $_ }{ $hash->{$_} }++ for keys %$hash;
}
my @wanted = (1,0);
for my $key (sort { $a <=> $b } keys %count) {
    my @result = map { "agr$_ = " . ($count{$key}{$_} || 0) . ';' } @wanted;
    print "key $key: @result\n";
}

Output:

key 1: agr1 = 4; agr0 = 0;
key 2: agr1 = 3; agr0 = 1;
key 3: agr1 = 1; agr0 = 3;
key 4: agr1 = 2; agr0 = 2;

Upvotes: 3

chrsblck
chrsblck

Reputation: 4088

First, your hash examples are wrong. %hash = {}.

If you use the % sigil you want (). If you want a hash-ref, it would be $hash = {}.

Back to your question. You could do something like this.

Which is commonly referred to as a "seen" hash.

# appending your example above....
my @arr = (\%hash1, \%hash2, \%hash3, \%hash4);
my $seen = {}; 

# iterate over each hash
for my $hash (@arr) {
    # iterate over each key in hash
    for my $k (keys %$hash) {
        my $val = $hash->{$k};

        # check $val and increment 
        if ($val) {
            $seen->{$k}{1}++;
        } else {
            $seen->{$k}{0}++;
        }   
    }   
}

for my $k ( sort keys %$seen ) { 
    # in case any value is 0/undef
    print "$k:  1 = "
      . ( $seen->{$k}->{0} ? $seen->{$k}->{0} : 0 ) . " 0 = "
      . ( $seen->{$k}->{0} ? $seen->{$k}->{0} : 0 ) . "\n";
}

Which OUTPUTS:

$ perl test.pl
1:  1 = 0 0 = 0
2:  1 = 1 0 = 1
3:  1 = 3 0 = 3
4:  1 = 2 0 = 2

Upvotes: 0

Jim Garrison
Jim Garrison

Reputation: 86774

Pseudoperl:

# Build a list of refs to your hashes
@a = { \%hash1, \%hash2, ... }

# A container to hold the keys and counts
$keys = {} 

# Initialize the key container
for my $h in @a
    for my $k in %$h
        $keys->{$k} = {0 => 0, 1 => 0} unless exists $keys->{$k}

# Iterate once over all the keys and count occurrences
# assumes input hash values can be only 0 or 1
for my $k in %$keys
    for my $h in @a
        $keys->{$k}->{$h->{$k}}++ if exists $h->{$k};

Upvotes: 1

Related Questions