Sorting and printing a hash of hashes in Perl - perl

Using Perl, I have a HoH similar to this:
%HoH = (
'A' => {
'a' => 4,
'b' => 18,
'c' => 2
},
'B' => {
'a' => 1,
'b' => 2
},
'C' => {
'a' => 1
},
'D' => {
'a' => 1,
'b' => 2,
'c' => 5,
'd' => 9
},
#........ on and on and on .....
);
For each of the capital keys, I want to print the one lower-case key that has the largest value associated with it.
example output:
b,b,a,d...
Any direction at this point would be appreciated, new to the game.

use List::Util qw(reduce);
for my $k1 (sort keys %HoH) {
my $h = $HoH{$k1};
my $k2 = reduce { $h->{$a} > $h->{$b} ?$a :$b } keys %$h;
print "$k1, $k2\n";
}

For example:
for my $k (sort keys %HoH) {
my $h = $HoH{$k};
my $g= (sort {$h->{$b} <=> $h->{$a}} keys %$h)[0];
print "$k: $g \n";
}
(Your original output does not much sense, because the order of the keys of %HoH is not fixed)

Using List::Util's reduce;
use List::Util qw(reduce);
use strict;
use warnings;
my %HoH = ...
for my $k (sort keys %HoH) {
my $h = $HoH{$k};
my $maxKey = reduce {$h->{$a} > $h->{$b} ? $a : $b} keys %$h;
print "$k -> $maxKey\n";
}

Related

custom sort method for hashes which will automatically use the approporiate hash

My hash contains binary numbers as keys:
my %h = ("1010" => 1, "1110" => 0, "0001" => 3, "1100" => 2);
In perl I can use custom function for sorting hash. This is my function for sorting binary numbers from lowest to largest:
sub sort_binary_numbers {
my $a_dec = oct("0b".$a);
my $b_dec = oct("0b".$b);
return $a_dec <=> $b_dec;
}
I can sort hash using this function following way:
print Dumper sort sort_binary_numbers keys %h;
And the result will be:
$VAR1 = '0001';
$VAR2 = '1010';
$VAR3 = '1100';
$VAR4 = '1110';
I want to sort hash using values not keys. I can do following:
print Dumper sort { $h{$b} <=> $h{$a} } keys %h;
As you can see I have to use hash name in sorting block. The problem is how to rewrite this sorting block to function (as above examples) and automatically get the appropriate hash name in function. I've tried access hash name using #_ but it was not printed e.g.
sub sort_by_value {
print Dumper #_; # This was not printed
print ref #_; # This was not printed
return $b <=> $a;
}
And call it following way:
print Dumper sort sort_by_value keys %h;
The interesting part is that when I wrap this sorting in to another function and call it in loop from this function I will get the output of data dumper that was previously missing (but I still did not get the output of ref command):
sub calling_from_function {
my %h = %{$_[0]};
foreach my $key (sort sort_by_value keys %h){
}
}
&calling_from_function(\%h);
Then I get this output:
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
$VAR1 = {
'0001' => 3,
'1010' => 1,
'1110' => 0,
'1100' => 2
};
Questions:
How can I replace sorting block in this command print Dumper sort { $h{$b} <=> $h{$a} } keys %h; with function and get the appropriate name of hash inside sortign function?
Why wrapping from another function works?
Why ref does not works?
The sorting subroutine doesn't take parameters normally (i.e. unless prototypes are involved) through #_, but through $a and $b. ref #array can never return anything, as an array is never a reference.
Wrapping by another function works, because you populate #_ by parameters to the wrapper.
Use a wrapper to sort any hash:
sub sort_by_value {
my %h = #_;
return sort { $h{$b} <=> $h{$a} } keys %h
}
print Dumper(sort_by_value(%h));
You can also send the hash reference to the subroutine:
sub sort_by_value {
my ($h) = #_;
return sort { $h->{$b} <=> $h->{$a} } keys %$h
}
print Dumper sort_by_value(\%h);
So you want to have a generic sorting function such as
my $sorter = sub { $_[0]{$b} <=> $_[0]{$a} };
When it comes time to sort, just use
my #sorted_keys = sort { $sorter->(\%h) } keys(%h);
You can use hash as a list, convert it to k/v aref pairs, perform sort on values (second element), and pick keys from sorted list (roughly it is Schwartzian transform in disguise).
use strict;
use warnings;
use List::Util 'pairs';
my %h = ("1010" => 1, "1110" => 0, "0001" => 3, "1100" => 2);
my #k = map $_->[0],
sort { $b->[1] <=> $a->[1] }
pairs %h;
without additional modules,
my #k = map $_->[0],
sort { $b->[1] <=> $a->[1] }
map [ $_, $h{$_} ],
keys %h;

Sort an array of hashes

I have a reference that has the following data structure when dumped:
VAR1 = [
{
'0' => 0
},
{
'1' => 1
},
{
'-1' => 2
},
{
'2' => 3
},
];
I am trying to loop over them and eventually sort by key. Here is an example of my code:
use strict;
use warnings;
use Data::Dumper;
my $skew_ref;
push #{$skew_ref}, { 0 => 0, 1 => 1, -1 => 2, 2 => 3, };
my #sorted;
for my $ref ( #{$skew_ref} ) {
while ( my ($k, $v ) = each %{$ref} ) {
print "$k => $v\n";
}
#sorted = sort { %{$b} <=> %{$a} } keys %{$ref};
}
print Dumper(\#sorted);
What am I doing incorrectly? I want the smallest key value and it is giving me the largest.
The output should just be 2 in this case.
use List::Util qw( min );
my $skews = { 0 => 0, 1 => 1, -1 => 2, 2 => 3 };
my $val = $skews->{ min keys %$skews };
Contrary to your implications, there cannot be more than one result since a hash cannot have two elements with the same key.
my #sorted = map $_->[0],
sort { $a->[1] <=> $b->[1] }
map [ $_, keys %$_ ], #arr;
Answering your direct question: you swapped a and b in the sort closure:
#sorted = sort { %{$a} <=> %{$b} } keys %{$ref};

Perl extract range of elements from a hash

If I have a hash:
%hash = ("Dog",1,"Cat",2,"Mouse",3,"Fly",4);
How can I extract the first X elements of this hash. For example if I want the first 3 elements, %newhash would contain ("Dog",1,"Cat",2,"Mouse",3).
I'm working with large hashes (~ 8000 keys).
"first X elements of this hash" doesn't mean anything. First three elements in order by numeric value?
my %hash = ( 'Dog' => 1, 'Cat' => 2, 'Mouse' => 3, 'Fly' => 4 );
my #hashkeys = sort { $hash{$a} <=> $hash{$b} } keys %hash;
splice(#hashkeys, 3);
my %newhash;
#newhash{#hashkeys} = #hash{#hashkeys};
You might want to use something like this:
my %hash = ("Dog",1,"Cat",2,"Mouse",3,"Fly",4);
for ( (sort keys %hash)[0..2] ) {
say $hash{$_};
}
You should have an array 1st:
my %hash = ("Dog" => 1,"Cat"=>2,"Mouse"=>3,"Fly"=>4);
my #array;
foreach $value (sort {$hash{$a} <=> $hash{$b} }
keys %hash)
{
push(#array,{$value=>$hash{$value}});
}
#get range:
my #part=#array[0..2];
print part of result;
print $part[0]{'Cat'}."\n";

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.

Return all hash key/value pairs with maximum value

I have a hash (in Perl) where the values are all numbers. I need to create another hash that contains all key/value pairs from the first hash where the value is the maximum of all values.
For example, given
my %hash = (
key1 => 2,
key2 => 6,
key3 => 6,
);
I would like to create a new hash containing:
%hash_max = (
key2 => 6,
key3 => 6,
);
I'm sure there are many ways to do this, but am looking for an elegant solution (and an opportunity to learn!).
use List::Util 'max';
my $max = max(values %hash);
my %hash_max = map { $hash{$_}==$max ? ($_, $max) : () } keys %hash;
Or a one-pass approach (similar to but slightly different from another answer):
my $max;
my %hash_max;
keys %hash; # reset iterator
while (my ($key, $value) = each %hash) {
if ( !defined $max || $value > $max ) {
%hash_max = ();
$max = $value;
}
$hash_max{$key} = $value if $max == $value;
}
This makes one pass over the data, but wastes a lot of hash writes:
use strict;
use warnings;
my %hash = (
key1 => 2,
key2 => 6,
key3 => 6,
);
my %hash_max = ();
my $max;
foreach my $key (keys %hash) {
if (!defined($max) || $max < $hash{$key} ) {
%hash_max = ();
$max = $hash{$key};
$hash_max{$key} = $hash{$key};
}
elsif ($max == $hash{$key}) {
$hash_max{$key} = $hash{$key};
}
}
foreach my $key (keys %hash_max) {
print "$key\t$hash_max{$key}\n";
}
# sort numerically descending
my #topkey = sort {$hash{$b} <=> $hash{$a}} keys %hash;
Then copy the top values to %hash_max, with a loop terminator after the last max value:
for $key (#topkey) {
if ($hash{$key} == $hash{$topkey[0]}) {
$hash_max{$key} = $hash{$key}
} else { last }
}
ETA: Note to the unbelievers that last works because the keys in #topkey are sorted, so we can break the loop when the value is no longer like the first one. I.e. all the following values are lower.