Compare values of keys across multiple hashes in Perl - 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.

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;

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

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

Related

compare 2 arrays for intersect diff and commmon values

I want to compare 2 arrays and want diff , common and intersect values but below code is not working.
No error message all I can see Array as an value although I am calling $difference[0] so I doubt if the code is correct.
sub updatedevice() {
my $n = {};
my $Device_LINK = $server->object("ICF_PersistentDataSet",$devicelinks);
my $Temp_Device_LINK = $server->object("ICF_PersistentDataSet",$tempdevicelinks);
my #current_devicelist = #{ $Device_LINK->invoke("get") };
my #temp_devicelist = #{ $Temp_Device_LINK->invoke("get") };
my %temp_list;
my %current_list;
my $size = #current_devicelist;
for ($n=0; $n < $size; $n++) {
our $device=$current_devicelist[$n][0];
DEBUG( "DEBUG: - devicelinks values $device " ); --- > able to print this value of device "ABC-DCFE41->90"
my $size = #temp_devicelist;
for ($n=0; $n < $size; $n++) {
our $tempdevicelinks=$temp_devicelist[$n][0];
DEBUG( "DEBUG: - temp plc links values $tempdevicelinks " ); --- > able to print this value of device "GHJKL-poiu->78"
my %count = ();
foreach my $device (#current_devicelist, #temp_devicelist) {
$count{$device}++;
}
my #difference = grep { $count{$_} == 1 } keys %count;
my #intersect = grep { $count{$_} == 2 } keys %count;
my #union = keys %count;
DEBUG( "DEBUG: - difference links values $difference[0] " );
DEBUG( "DEBUG: - intersect links values $intersect[0] " );
DEBUG( "DEBUG: - union links values $union[0] " );
}
}
}
The problem is that you're assigning array reference (returned from invoke to an array).
Your statement of "see 'array' as a value" is a dead giveaway that you're manipulating array references (instead of arrays) - when printed, they turn into strings like this: 'ARRAY(0x349014)'
The problem is that you're taking an array reference (a scalar), and assigning it to an array - which imposes list context on your value, and turns that scalar into a list with its only element being that scalar. Thus you simply store the array reference as the first and only element of the array - instead of storing the list of values that's being referenced like you intended.
To demonstrate:
my #current_devicelist = (1,3); # Assign real arrays
my #temp_devicelist = (2,3);
my %count = ();
foreach my $device (#current_devicelist, #temp_devicelist) {
$count{$device}++;
}
my #difference = grep { $count{$_} == 1 } keys %count;
my #intersect = grep { $count{$_} == 2 } keys %count;
my #union = keys %count;
use Data::Dumper;
print Data::Dumper->Dump([\#difference, \#intersect, \#union]
,["difference","intersect","union"]);
This prints:
$difference = [
'1',
'2'
];
$intersect = [
'3'
];
$union = [
'1',
'3',
'2'
];
Now, if you mimique what your code was doing instead by changing the first 2 lines to:
my #current_devicelist = [1,3]; # Assign reference
# Works the same as if you said
# my #current_devicelist = ([1,3]);
# or
# my $current_devicelist[0] = [1,3];
my #temp_devicelist = [2,3];
... you get:
$difference = [
'ARRAY(0x349014)',
'ARRAY(0x349114)'
];
$intersect = [];
$union = [
'ARRAY(0x349014)',
'ARRAY(0x349114)'
];
To fix your problem, you can do one of 4 things:
Simply dereference your returned array references, using #{} dereference:
my #current_devicelist = #{ $Device->invoke("get") };
my #temp_devicelist = #{ $Temp_Device->invoke("get") };
Change invoke() method - if you can - to return an array instead of array reference:
# Old code:
# return $myArrRef;
# New Code:
return #$myArrRef;
Change invoke() method - if you can - to return an array OR an arrayref based on context (using wantarray):
# Old code:
# return $myArrRef;
# New Code:
return wantarray : #$myArrRef : $myArrRef;
Change your code to use array references
my $current_devicelist = $Device->invoke("get");
my $temp_devicelist = $Temp_Device->invoke("get");
my %count = ();
foreach my $device (#$current_devicelist, #$temp_devicelist) {
$count{$device}++;
}

Perl: Sorting multi dimensional hash

I'm currently trying to sort by values in a multi dimensional hash and I'm coming up a bit stumped.
I've done this on a single level as follows:
my %exampleHash = ();
$exampleHash{1}{value} = 10;
$exampleHash{2}{value} = 30;
$exampleHash{3}{value} = 0;
$exampleHash{4}{value} = 20;
foreach my $key ( reverse sort {$exampleHash{$a}{value} <=> $exampleHash{$b}{value}} keys %exampleHash )
{
printf( "%s => %d\n", $key, $exampleHash{$key}{value} );
}
This produces the following output, sorted on the value as expected:
2 => 30
4 => 20
1 => 10
3 => 0
I've then tried the following to do the same thing but with an extra key:
my %exampleHashTwo = ();
$exampleHashTwo{1}{"One"}{value} = 10;
$exampleHashTwo{2}{"Two"}{value} = 30;
$exampleHashTwo{3}{"Three"}{value} = 0;
$exampleHashTwo{4}{"Four"}{value} = 20;
foreach my $intKey ( keys %exampleHashTwo )
{
foreach my $stringKey ( reverse sort {$exampleHashTwo{$intKey}{$a}{value} <=> $exampleHashTwo{$intKey}{$b}{value}} keys %{$exampleHashTwo{$intKey}} )
{
printf( "{%d} - {%5s} => %d\n", $intKey, $stringKey, $exampleHashTwo{$intKey}{$stringKey}{value} );
}
}
However this seems to sort the string key's in alphabetical order. So I'm assuming I'm on the right lines, but I've misplaced something perhaps?
{4} - { Four} => 20
{1} - { One} => 10
{3} - {Three} => 0
{2} - { Two} => 30
Any ideas?
Your outer loop, which loops over the integer keys in somewhat random order, is dictating the order of your output. The inner loop is sorting but only given one value each time.
To sort all the values for all combinations of both keys, you need a single loop and to sort over a generated list of such combinations:
use strict;
use warnings;
my %exampleHashTwo = ();
$exampleHashTwo{1}{"One"}{value} = 10;
$exampleHashTwo{2}{"Two"}{value} = 20;
$exampleHashTwo{2}{"TwoB"}{value} = 5;
$exampleHashTwo{3}{"Three"}{value} = 0;
$exampleHashTwo{4}{"Two"}{value} = 15;
for my $keypair (
sort { $exampleHashTwo{$b->[0]}{$b->[1]}{value} <=> $exampleHashTwo{$a->[0]}{$a->[1]}{value} }
map { my $intKey=$_; map [$intKey, $_], keys %{$exampleHashTwo{$intKey}} } keys %exampleHashTwo
) {
printf( "{%d} - {%5s} => %d\n", $keypair->[0], $keypair->[1], $exampleHashTwo{$keypair->[0]}{$keypair->[1]}{value} );
}
Output:
{2} - { Two} => 20
{4} - { Two} => 15
{1} - { One} => 10
{2} - { TwoB} => 5
{3} - {Three} => 0
(Replaced reverse with switching order of $a and $b.)

Search through hash, if value is zero delete it

I have some code as follows. I am trying to search through the hash and if I come across a value that is zero, I want to delete the whole key/value element.
my %hashy = (
a => my $a,
b => my $b,
c => my $c,
d => my $d,
e => my $e
);
$hashy{'a'} = 0;
$hashy{'b'} = 1;
$hashy{'c'} = 0;
$hashy{'d'} = 2;
$hashy{'e'} = 1;
my #keys = keys %hashy;
my #values = values %hashy;
my $ind = 0;
foreach my $v (#values) {
delete $hashy{$keys[$ind]} if ($v == 0);
}
So the expected output of printing %hashy would be: b1d2e1 (ignoring order of elements)
At the moment I get: c0a0b1d2 which isn't even close... any help would be appreciated :)
Iterating over the values won't help because you lose the association between the values and the keys, although I guess that's what $ind was supposed to be helping you track.
Just iterate over the keys instead:
foreach my $k (keys %hashy) {
delete $hashy{$k} if ($hashy{$k} == 0);
}
Another way to do it:
delete #hash{ grep $hash{$_} == 0, keys %hash };

how to search a hash using the values and return the corresponding key upon success in perl

I am looking for search implementation on hash using perl. I have following data in my hash
%hash = {0 => "Hello", 1=> "world"}.
Now i want to search the hash using the values (means world and hello) and return corresponding key.
Example: I want to search for world and the result should be 1
Iterate of the keys of the hash with a for ( keys %hash ) ... statement and check the values as you go. If you find what you are looking for, return
my $hash = { 0 => "World", 1 => "Hello" };
for ( keys %$hash ) {
my $val = $hash->{$_};
return $_ if $val eq 'World'; # or whatever you are looking for
}
another option would be to use while ( ... each ... )
my $hash = { 0 => "World", 1 => "Hello" };
while (($key, $val) = each %$hash) {
return $key if $val eq 'World'; # or whatever you are looking for
}
the use of { } literal creates a hash reference and not a hash
$h = { a => 'b', c => 'd' };
to create a literal hash you use ( )
%h = ( a => 'b', c => 'd' );
execution of while ... each on hashref
$h = { a => 'b', c => 'd' };
print "$k :: $v\n" while (($k, $v) = each %$h );
c :: d
a :: b
If:
The hash isn't very large, and
The values are unique
You can simply create a lookup hash with reverse:
my %lookup = reverse %hash;
my $key = $lookup{'world'}; # key from %hash or undef
use strict;
use warnings;
my %hash = (0 => "Hello", 1=> "world");
my $val = 'world';
my #keys = grep { $hash{$_} eq $val } keys %hash;
print "Keys: ", join(", ", #keys), "\n";
This will return all keys i.e. If the value is same for multiple keys.

Tie::IxHash ordered associative arrays in Hash of Hashes?

How can I preserve the order in which the hash elements were added
FOR THE SECOND VAR ?
( Hash of Hashes )
For example:
use Tie::IxHash;
my %hash;
tie %hash, "Tie::IxHash";
for my $num (0 .. 5){
$hash{"FirstVal$num"}++;
}
for my $num (0 .. 5){
$hash{"FirstValFIXED"}{"SecondVal$num"}++;
}
print Dumper(%hash);
When dumping out the result, $VAR14 didn't preserve the insertion order:
$VAR1 = 'FirstVal0';
$VAR2 = 1;
$VAR3 = 'FirstVal1';
$VAR4 = 1;
$VAR5 = 'FirstVal2';
$VAR6 = 1;
$VAR7 = 'FirstVal3';
$VAR8 = 1;
$VAR9 = 'FirstVal4';
$VAR10 = 1;
$VAR11 = 'FirstVal5';
$VAR12 = 1;
$VAR13 = 'FirstValFIXED';
$VAR14 = {
'SecondVal5' => 1,
'SecondVal4' => 1,
'SecondVal2' => 1,
'SecondVal1' => 1,
'SecondVal3' => 1,
'SecondVal0' => 1
};
I know I can trick that example with some sort operation but in my real problem the elements are not numbered or can't be ordered some how.
Is there any simple function/operation for hash multi level order insertion ?
Thanks,
Yodar.
Look at Tie::Autotie. It automatically ties new hashes created by autovivification. The perldoc page shows an example using Tie::IxHash.
You need an extra "\", as below.
print Dumper(\%hash);
Do you mean hash of hashes? You need to tie to Tie::IxHash every value of outer hash.
use strict;
use warnings;
use Tie::IxHash;
my $hash={};
my $t = tie(%$hash, 'Tie::IxHash', 'a' => 1, 'b' => 2);
%$hash = (first => 1, second => 2, third => 3);
$hash->{fourth} = 4;
print join(', ',keys %$hash),"\n";
my %new_hash=('test'=>$hash);
$new_hash{'test'}{fifth} = 5;
print join(', ',keys %{$new_hash{'test'}}),"\n";
$new_hash{'test'}{fifth}++;
print join(', ',values %{$new_hash{'test'}}),"\n";
foreach my $sortline (sort {$a<=>$b} keys %{$hash->{"first_field"}}){
my $name;
# Soultion to touch a Key with keys within it:
#---------------------------------------------
foreach my $subkey (keys %{$hash->{"first_field"}->{$sortline}})
{$name = $subkey;}
#---------------------------------------------
}
This useful answer helped me.