Perl - retrieve values from a hash of hashes - perl

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

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: Sort values in multidimensional hash

I have a subroutine that looks likes this:
...
sub UserLogins {
my %loginData;
my #logins = qx(last) or die;
foreach my $row (#logins) {
if ( $row=~ /^(\w+)\s+/ and (("$1" ne "reboot") and ("$1" ne "wtmp")) ) {
$loginData{$1}{"logins"}++;
}
}
return \%loginData
}
...
Using this subroutine in main script, I get following output:
...
$VAR1 = {
'user1' => {
'oldpassword' = 0,
'filesize' => '14360',
'logins' => 1
},
'user2' => {
'oldpassword' = 0,
'filesize' => '1220',
'logins' => 15
},
'user3' => {
'oldpassword' = 1,
'filesize' => '1780',
'logins' => 7
}
}
...
I wonder how I should sort my %loginData hash so user with largest number of logins gets printed first (in this case user2, user3, user1).
I have also tried to sort values in this way:
foreach my $test_sort (sort {$a <=> $b} values %loginData) {
say $test_sort;
}
But this function doesn't work at all.
Another thing I tried and didn't work:
print "$_\n" foreach sort {$loginData{$b}->{logins} <=> $loginData{$a}->{logins}} keys %loginData;
Update
This function actually works, but shows errors massages:
print "$_\n" foreach sort {$userData{$b}->{'logins'} <=> $userData{$a}->{'logins'}} keys %userData;
Errors:
Use of uninitialized values in numeric comparison (<=>)
Here's a canonical solution for accessing elements of a hash in some order based on an attribute of the value (in this case logins):
for (sort { $loginData{$b}->{logins} <=> $loginData{$a}->{logins} } keys %loginData)
{
...
}
Note reversal of $b and $a to achieve a reverse sort (most logins first).
You can't sort a hash. From the Perl docs:
Hash entries are returned in an apparently random order.
You can, compute a list of keys that index the hash in the order you like.
Something like this:
use Data::Dumper;
our $hash = {
'user1' => {
'oldpassword' => 0,
'filesize' => '14360',
'logins' => 1
},
'user2' => {
'oldpassword' => 0,
'filesize' => '1220',
'logins' => 15
},
'user3' => {
'oldpassword' => 1,
'filesize' => '1780',
'logins' => 7
}
};
sub KeysByLogins {
my $hash = shift;
map { $_->[1] }
sort { $a->[0] <=> $b->[0] }
map { [ $hash->{$_}->{logins}, $_ ] } keys %$hash;
}
foreach my $key (KeysByLogins($hash)) {
print Data::Dumper->Dump([$hash->{$key}], [$key]) . "\n";
}
Then...
$ perl foo.pl
$user1 = {
'filesize' => '14360',
'oldpassword' => 0,
'logins' => 1
};
$user3 = {
'logins' => 7,
'oldpassword' => 1,
'filesize' => '1780'
};
$user2 = {
'oldpassword' => 0,
'filesize' => '1220',
'logins' => 15
};

accessing the highest value in hash of an hash in 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.

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: Sorting hash of hash by value descending order

data :
%HoH => (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
)
How do I sort the hash of hash by value in descending order?
Output
100
12
5
You can't sort a hash, it won't hold the order. If you wanted to keep them sorted, you'll have to sort the keys based on the number and store the keys in an array.
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => { value => 12 },
xyz => { value => 100},
pqr => { value => 5},
def => { value => 15},
hij => { value => 30},
);
my #sorted_keys = map { $_->[0] }
sort { $b->[1] <=> $a->[1] } # use numeric comparison
map { my $temp;
if ( exists $HoH{$_}{'value'} ) {
$temp = $HoH{$_}{'value'};
} elsif ( exists $HoH{$_}{'number'} ) {
$temp = $HoH{$_}{'number'};
} elsif ( exists $HoH{$_}{'digit'} ) {
$temp = $HoH{$_}{'digit'};
} else {
$temp = 0;
}
{[$_, $temp]} }
(keys %HoH);
for my $key (#sorted_keys) {
my $temp;
if ( exists $HoH{$key}{'value'} ) {
$temp = $HoH{$key}{'value'};
} elsif ( exists $HoH{$key}{'number'} ) {
$temp = $HoH{$key}{'number'};
} elsif ( exists $HoH{$key}{'digit'} ) {
$temp = $HoH{$key}{'digit'};
} else {
$temp = 0;
}
print $key . ":" . $temp ."\n";
}
Output:
xyz:100
hij:30
def:15
abc:12
pqr:5
This technique to do the sorting is called Schwartzian Transform.
Given you're not actually using the keys for anything, you can flatten the data structure into a single array and then sort it:
use strict;
use warnings;
my %HoH = (
abc => {value => "12",},
xyz => {number => "100",},
pqr => {digit => "5",},
);
my #numbers = sort {$b <=> $a} map {values %$_} values %HoH;
print "$_\n" for #numbers;
Outputs:
100
12
5
However, if you want to use the additional key information, then you'll need fold your Hash of Hash into an array, and then you can sort however you like:
my #array;
while (my ($k, $ref) = each %HoH) {
while (my ($k2, $v) = each %$ref) {
push #array, [$k, $k2, $v];
}
}
#array = sort {$b->[2] <=> $a->[2]} #array;
use Data::Dump;
dd \#array;
Outputs:
[
["xyz", "number", 100],
["abc", "value", 12],
["pqr", "digit", 5],
]
I came up with this solution
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
);
my %rever;
for my $TopKey(keys %HoH){
for my $value(values %{ $HoH{$TopKey} }){
push #{ $rever{$value} }, $TopKey;
}
}
my #nums = sort {$b <=> $a} (keys(%rever));
print $_, "\n" for #nums;
I reversed the values in case you still needed to use the key names.
This is how it looks after using Dumper.
$VAR1 = '100';
$VAR2 = [
'xyz'
];
$VAR3 = '12';
$VAR4 = [
'abc'
];
$VAR5 = '5';
$VAR6 = [
'pqr'
];