accessing the highest value in hash of an hash in perl? - perl

so i have a hash of an hash that looks something like this:
my %hash = (
'fruits' => {
'apple' => 34,
'orange' => 30,
'pear' => 45,
},
'chocolates' => {
'snickers' => 35,
'lindt' => 20,
'mars' => 15,
},
);
I want to access only the fruit which is max in number and chocolate which is max in number. the output should look like:
fruits: pear
chocolates : snickers
foreach my $item (keys %hash){
#print "$item:\t"; # this is the object name
foreach my $iteminitem (keys %{$hash{$item}})
{
my $longestvalue = (sort {$a<=>$b} values %{$hash{$item}})[-1]; #this stores the longest value
print "the chocolate/fruit corresponding to the longestvalue" ;
#iteminitem will be chocolate/fruit name
}
print "\n";
}
I know it is not difficult but I am blanking out!

The following sorts the keys of each hashref by descending value, so the max is the first element returned:
my %hash = (
chocolates => { lindt => 20, mars => 15, snickers => 35 },
fruits => { apple => 34, orange => 30, pear => 45 },
);
while (my ($key, $hashref) = each %hash) {
my ($max) = sort {$hashref->{$b} <=> $hashref->{$a}} keys %$hashref;
print "$key: $max\n";
}
Outputs:
fruits: pear
chocolates: snickers

Here is another way:
use strict;
use warnings;
use List::Util qw(max);
my %hash = (
'fruits' => {
'apple' => 34,
'orange' => 30,
'pear' => 45,
},
"chocolates" => {
'snickers' => 35,
'lindt' => 20,
'mars' => 15,
},
);
for (keys %hash) {
my $max = max values %{$hash{$_}}; # Find the max value
my %rev = reverse %{$hash{$_}}; # Reverse the internal hash
print "$_: $rev{$max}\n"; # Print first key and lookup by max value
}
Output:
fruits: pear
chocolates: snickers

For this you likely want List::UtilsBy:
use List::UtilsBy 'max_by';
my %hash = (
chocolates => { lindt => 20, mars => 15, snickers => 35 },
fruits => { apple => 34, orange => 30, pear => 45 },
);
foreach my $key ( keys %hash ) {
my $subhash = $hash{$key};
my $maximal = max_by { $subhash->{$_} } keys %$subhash;
print "$key: $maximal\n";
}
For this small example it probably doesn't matter too much, but for much larger cases, there's a big difference. This will run in O(n) time for the size of the hash, whereas the "sort and take first index" solution will take O(n log n) time, much slower, to sort the list of keys, only to then throw away all but the first result.

Related

two dimensional hash data output?

this my code.At the end of this process I want to collect name and sum results with ascending order and want to print.I tried to take the as #sum1 and #sum2 but it couldn't know #sum functions.How can I do that or any idea will be very valuable.
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw( sum );
use Data::Dumper qw(Dumper);
my %grades;
$grades{"Ahmet"}{quiz1} = 97;
$grades{"Ahmet"}{quiz2} = 67;
$grades{"Ahmet"}{quiz3} = 93;
$grades{"Mehmet"}{quiz1} = 88;
$grades{"Mehmet"}{quiz2} = 82;
$grades{"Mehmet"}{quiz3} = 99;
print Dumper \%grades;
print "----------------\n";
foreach my $name ( sort keys %grades ) {
my %hash1 = (
'Ahmet' => [ 97, 67, 93 ],
'Mehmet' => [ 88, 82, 99 ],
);
my #sums;
for my $key ( keys %hash1 ) {
my $sum = sum #{ $hash1{$key} };
push #sums, "$key: $sum\n";
}
foreach my $sum ( keys %{ $grades{$name} } ) {
print "$name : $grades{$name}{$sum}\n";
}
print #sums;
}
foreach my $name ( sort keys %grades ) {
print "$grades{$name}\n";
}
my %grades2;
$grades2{"Merve"}{quiz1} = 33;
$grades2{"Merve"}{quiz2} = 41;
$grades2{"Merve"}{quiz3} = 59;
$grades2{"Aslı"}{quiz1} = 79;
$grades2{"Aslı"}{quiz2} = 31;
$grades2{"Aslı"}{quiz3} = 62;
print Dumper \%grades2;
print "----------------\n";
foreach my $name2 ( sort keys %grades2 ) {
my %hash = (
'Merve' => [ 33, 41, 59 ],
'Aslı' => [ 79, 31, 62 ],
);
my #sums2;
for my $key ( keys %hash ) {
my $sum = sum #{ $hash{$key} };
push #sums2, "$key: $sum\n";
}
foreach my $sum ( keys %{ $grades2{$name2} } ) {
print "$name2 : $grades2{$name2}{$sum}\n";
}
print #sums2;
}
foreach my $name2 ( sort keys %grades2 ) {
print "$grades2{$name2}\n";
}
my %info;
$info{$_} .= "A" for keys %grades;
$info{$_} .= "B" for keys %grades2;
for ( sort keys %info ) {
print "$_ : $info{$_}\n";
}
You seem to duplicate some information in your code. Once you populate the %grades, there's no need to create the %hash - it can be extracted from the %grades directly.
To populate the hashes, you don't have to repeat the keys all the time, describe the substructure as shown below.
Also, your code contains "ı", which isn't an ASCII character. It would be nicer to tell Perl that by using utf8 and also declaring the encoding of the output.
To sort the output by sums, use the sort function with a custom block that compares the hash elements:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use open ':encoding(UTF-8)', ':std';
use List::Util qw( sum );
my %grades = (
Ahmet => {
quiz1 => 97,
quiz2 => 67,
quiz3 => 93,
},
Mehmet => {
quiz1 => 88,
quiz2 => 82,
quiz3 => 99,
}
);
my %sum;
for my $name (keys %grades)
{
$sum{$name} = sum(values %{ $grades{$name} });
}
for my $name (sort { $sum{$a} <=> $sum{$b} } keys %sum)
{
print $name, ' ', $sum{$name}, "\n";
}
my %grades2 = (
Merve => {
quiz1 => 33,
quiz2 => 41,
quiz3 => 59,
},
Aslı => {
quiz1 => 79,
quiz2 => 31,
quiz3 => 62,
},
);
my %sum2;
for my $name (keys %grades2)
{
$sum2{$name} = sum(values %{ $grades2{$name} });
}
for my $name (sort { $sum2{$a} <=> $sum2{$b} } keys %sum2)
{
print $name, ' ', $sum2{$name}, "\n";
}
Here is a way to do it. You are creating a hash of hash ref, which is explicitly done like this (I have added one more student to make the example more interesting):
my %grades = (
'Ahmet' => {
quiz1 => 97,
quiz2 => 67,
quiz3 => 93,
},
'Mehmet' => {
quiz1 => 88,
quiz2 => 82,
quiz3 => 99,
},
'Abdul' => {
quiz1 => 99,
quiz2 => 89,
quiz3 => 99,
},
);
To print the students and their grades, you may use:
# Printing the students and grades
foreach my $student ( keys %grades ) {
print $student, ":\n";
foreach my $test ( keys %{$grades{$student}} ) {
print " - ", $test, "\t", $grades{$student}{$test}, "\n";
}
}
To generate the sum for each student:
# Generating a sum for each student
my %sums;
foreach my $student ( keys %grades ) {
$sums{$student} = sum0 map { $grades{$student}{$_} } keys %{$grades{$student}};
}
I use sum0 which will return 0 instead of undef when the list it is given is empty. You should use use List::Util qw( sum0 ); at the beginning.
Then printing the generated sum in ascending order:
# Printing the sum
foreach my $student ( sort { $sums{$a} <=> $sums{$b} } keys %sums ) {
print $student, ":\t", $sums{$student}, "\n";
}
For the opposite order, you may use sort { $sums{$b} <=> $sums{$a} } keys %sums. For the rest of your code, you would do the same...
The output of the students:
Ahmet:
- quiz1 97
- quiz3 93
- quiz2 67
Mehmet:
- quiz2 82
- quiz3 99
- quiz1 88
Abdul:
- quiz2 89
- quiz3 99
- quiz1 99
Then the sum of the grades:
Ahmet: 257
Mehmet: 269
Abdul: 287

Perl - retrieve values from a hash of hashes

How do we retrieve the values of keys in a Hash of Hashes in Perl?
I tried to use the keys function. I wanted to remove the duplicates and then sort them, which i could
do using the uniq and sort functions. Am I missing anything?
#!/usr/bin/perl
use warnings;
use strict;
sub ids {
my ($data) = #_;
my #allID = keys %{$data};
my #unique = uniq #allID;
foreach ( #unique ) {
#allUniqueID = $_;
}
my #result = sort{$a<=>$b}(#allUniqueId);
return #result;
}
my $data = {
'first' => {
'second' => {
'third1' => [
{ id => 44, name => 'a', value => 'aa' },
{ id => 48, name => 'b', value => 'bb' },
{ id => 100, name => 'c', value => 'cc' }
],
id => 19
},
'third2' => [
{ id => 199, data => 'dd' },
{ id => 40, data => 'ee' },
{ id => 100, data => { name => 'f', value => 'ff' } }
],
id => 55
},
id => 1
};
# should print “1, 19, 40, 44, 48, 55, 100, 199”
print join(', ', ids($data)) . "\n";
I know it's incomplete, but I am not sure how to proceed. Any help would be appreciated.
This routine will recursively walk the data structure and pull out all of the values that correspond to a hash key id, without sorting the results or eliminating duplicates:
sub all_keys {
my $obj = shift;
if (ref $obj eq 'HASH') {
return map {
my $value = $obj->{$_};
$_ eq 'id' ? $value : ref $value ? all_keys($value) : ();
} keys %$obj;
} elsif (ref $obj eq 'ARRAY') {
return map all_keys($_), #$obj;
} else {
return;
}
}
To do the sorting/eliminating, just call it like:
my #ids = sort { $a <=> $b } uniq(all_ids($data));
(I assume the uniq routine is defined elsewhere.)
Here's my version of the recursive approach
use warnings;
use strict;
sub ids {
my ($data) = #_;
my #retval;
if (ref $data eq 'HASH') {
push #retval, $data->{id} if exists $data->{id};
push #retval, ids($_) for values %$data;
}
elsif (ref $data eq 'ARRAY') {
push #retval, ids($_) for #$data;
}
#retval;
}
my $data = {
'first' => {
'second' => {
'third1' => [
{ id => 44, name => 'a', value => 'aa' },
{ id => 48, name => 'b', value => 'bb' },
{ id => 100, name => 'c', value => 'cc' }
],
id => 19
},
'third2' => [
{ id => 199, data => 'dd' },
{ id => 40, data => 'ee' },
{ id => 100, data => { name => 'f', value => 'ff' } }
],
id => 55
},
id => 1
};
my #ids = sort { $a <=> $b } ids($data);
print join(', ', #ids), "\n";
output
1, 19, 40, 44, 48, 55, 100, 100, 199
Update
A large chunk of the code in the solution above is there to work out how to extract the list of values from a data reference. Recent versions of Perl have an experimental facility that allows you to use the values operator on both hashes and arrays, and also on references ro both, so if you're running version 14 or later of Perl 5 and are comfortable disabling experimental warnings, then you can write ids like this instead
use warnings;
use strict;
use 5.014;
sub ids {
my ($data) = #_;
return unless my $type = ref $data;
no warnings 'experimental';
if ( $type eq 'HASH' and exists $data->{id} ) {
$data->{id}, map ids($_), values $data;
}
else {
map ids($_), values $data;
}
}
The output is identical to that of the previous solution

How do I get all values of a key in a perl data structure?

I want to write a function that will return a list of all “id” values in the data structure below at any level, sorted numerically. Also if the same value is found in multiple locations in the data structure it should only be included in the returned list once.
sub ids {
my ($data) = #_;
 
# Define this function

 }

 
 my $data = {
'top' => {
'window' => {
'elements' => {
{ id => 44, name => 'link', value => 'www.cnn.com' },

 { id => 48, name => 'title', value => 'CNN Home Page' },
{ id => 100, name => 'author', value => 'Admin' }
},

 id => 19

 },

 'cache' => {

 { id => 199, data => '5' },

 { id => 40, data => '9' },
{ id => 100, data => { name => 'author', value => 'Admin' }
}
 },
id => 55
 },

 id => 1

 };

 
 # should print “1, 19, 40, 44, 49, 55, 100, 199”
print join(', ', ids($data)) . “\n”;
Some of data structure should be arrays, not hashes as in OP,
use strict;
use warnings;
sub ids_r {
my ($data) = #_;
return map {
my $r = ref($data->{$_});
$r eq "HASH" ? ids_r($data->{$_}) :
$r ? map ids_r($_), #{$data->{$_}} :
$_ eq "id" ? $data->{$_} :
();
} keys %$data;
}
sub ids {
my ($data) = #_;
my %seen;
return
sort { $a <=> $b }
grep !$seen{$_}++, ids_r($data);
}
my $data = {
'top' => {
'window' => {
'elements' => [
{ id => 44, name => 'link', value => 'www.cnn.com' },
{ id => 48, name => 'title', value => 'CNN Home Page' },
{ id => 100, name => 'author', value => 'Admin' }
],
id => 19
},
'cache' => [
{ id => 199, data => '5' },
{ id => 40, data => '9' },
{ id => 100, data => { name => 'author', value => 'Admin' } }
],
id => 55
},
id => 1
};
print join(', ', ids($data));
output
1, 19, 40, 44, 48, 55, 100, 199
Here's a simple recursive solution. It's pretty easy to see what's going on here.
# There is a faster version of `uniq` provided by List::MoreUtils on CPAN.
sub uniq {
my %seen;
grep !$seen{$_}++, #_;
}
sub ids {
my $val = shift;
my $ref = ref $val;
my #r;
if ($ref eq 'HASH')
{
#r = map ids($_), grep ref, values(%$val);
push #r, $val->{id} if exists $val->{id};
}
elsif ($ref eq 'ARRAY')
{
#r = map ids($_), grep ref, #$val;
}
sort { $a <=> $b } uniq(#r);
}
#mpapec provides a similar solution which uses recursion without doing the sorting (the sub called ids_r in his answer), and then calls that from a separate wrapper function (the sub called ids in his answer) which provides the sorting all at the end. This is more efficient, but arguably more complex. (Indeed, because he had two similarly named functions, the first version of the answer included a mistake which negated the benefit of splitting the sorting out.)
Here's yet another technique, using a queue-based approach instead of recursion. If your data structure is very large, you may find that this works significantly faster.
# There is a faster version of `uniq` provided by List::MoreUtils on CPAN.
sub uniq {
my %seen;
grep !$seen{$_}++, #_;
}
sub ids {
my #r;
while (#_) {
my $val = shift;
my $ref = ref($val);
if ($ref eq 'HASH')
{
push #r, $val->{id} if exists $val->{id};
push #_, grep ref, values %$val;
}
elsif ($ref eq 'ARRAY')
{
push #_, grep ref, #$val;
}
}
sort { $a <=> $b } uniq(#r);
}

Perl:Access values of hash inside a hash

I have just picked up Perl.
I have a little confusion with accessing hash values. Below is the code where I am trying to access the values of a hash inside a hash.
Since am using a simple text editor to code, I am not able to figure out what can be the problem. Please help
my %box = (
Milk => {
A => 5,
B => 10,
C => 20,
},
Chocolate => {
AB => 10,
BC => 25,
CD => 40,
},
);
foreach my $box_key(keys %box) {
foreach my $inside_key (keys %box{box_key})
print "$box_key"."_$inside_key""is for rupees $box{box_key}{inside_key}";
}
If the syntax is
keys %hash
for a hash, it's
keys %{ ... }
for a hash reference. In this case, the reference is stored in $box{$box_key}, so you'd use
keys %{ $box{$box_key} }
Also, you're accessing elements named box_key and inside_key in a couple of places where you actually want the elements named by $box_key and $inside_key.
Finally, you can use curlies around variable names to instruct Perl where the variable name ends.
for my $box_key (keys %box) {
for my $inside_key (keys %{ $box{$box_key} }) {
print "${box_key}_$inside_key is for rupees $box{$box_key}{$inside_key}\n";
}
}
ikegami has explained it very well and I feel that you are still missing something in your code that's why you are having a problem, try the below code, hope it helps you.
my %box = (
Milk => {
A => 5,
B => 10,
C => 20,
},
Chocolate => {
AB => 10,
BC => 25,
CD => 40,
},
);
foreach my $box_key(keys %box) {
foreach my $inside_key (keys $box{$box_key}) {
print "${box_key}_$inside_key is for rupees $box{$box_key}{$inside_key}\n";
}
}
Output:
Chocolate_CD is for rupees 40
Chocolate_BC is for rupees 25
Chocolate_AB is for rupees 10
Milk_A is for rupees 5
Milk_C is for rupees 20
Milk_B is for rupees 10

Sorting by value Hash of Hashes Perl

Let's say I have a hash of hashes data structure constructed as followed:
%HoH => (
flintstones => {
family_members => "fred;wilma;pebbles;dino",
number_of_members => 4,
},
jetsons => {
family_members => "george;jane;elroy",
number_of_members => 3,
},
simpsons => {
family_members => "homer;marge;bart;lisa;maggie",
number_of_members => 5,
},
)
How do I sort the keys, the families in this case, by the value number_of_members from greatest to least? Then I would like to print out the highest two. Here's a general idea but I know it's wrong:
foreach $value (
sort {
$HoH{$a}{$number_of_members} cmp $HoH{$b}{$number_of_members}
} keys %HoH)
my $count = 0;
while ($key, $value) = each %HoH) {
if (count <= 2){
print "${HoH}{$key}\t$key{$value}";
}
}
continue {
$count++;
};
I want the code to print (the spaces are tab delimited):
simpsons homer;marge;bart;lisa;maggie
flintstones fred;wilma;pebbles;dino
You're on the right track. You use the $a and $b internal variables in the hash and compare the values numerically (<=> not cmp).
When printing, I find it easiest to store the keys in an array and use an array slice to access them.
use strict;
use warnings;
my %HoH = (
flintstones => {
family_members => "fred;wilma;pebbles;dino",
number_of_members => 4,
},
jetsons => {
family_members => "george;jane;elroy",
number_of_members => 3,
},
simpsons => {
family_members => "homer;marge;bart;lisa;maggie",
number_of_members => 5,
},
);
my #sorted = sort { $HoH{$b}{'number_of_members'} <=>
$HoH{$a}{'number_of_members'} } keys %HoH;
for (#sorted[0,1]) { # print only first two
print join("\t", $_, $HoH{$_}{'family_members'}), "\n";
}
Output:
simpsons homer;marge;bart;lisa;maggie
flintstones fred;wilma;pebbles;dino