Sorting by value Hash of Hashes Perl - 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

Related

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

getting the sort keys from a hash

This is the data dumper of \%spec_hash.
It is sorted by group which is a national exchange - and symbol.
foohost:~/walt $ vi /tmp/footoo
$VAR1 = {
'ARCX' => {
'IACI' => 1,
'MCHP' => 1,
},
'AMXO' => {
'YUM' => 1,
'SYK' => 1,
},
'XISX' => {
'FCEL' => 1,
'GPS' => 1,
}
};
I was trying to sort by keys these two hashes but cannot. For debugging purposes I really want to see what is getting pumped out of these hashes
foreach my $exch (sort keys %spec_hash) {
foreach my $exch (sort keys %{$spec_hash{$exch}}) {
If I comment out the dumper and try a regular sort :
#print Dumper(\%spec_hash) ;
foreach my $exch (sort keys %spec_hash) {
#foreach my $exch (sort keys %{$spec_hash{$exch}}) {
print "key: $exch, value: $spec_hash{$exch}\n"
}
this i what I get :
key: AMXO, value: HASH(0x9cc88a4)
key: ARCX, value: HASH(0x9cd6f1c)
key: XISX, value: HASH(0x9cbd5f0)
and trying to print this prints nothing at all :
foreach my $exch (sort keys %{$spec_hash{$exch}}) {
print "key: $exch, value: $spec_hash{$exch}\n"
}
If I understand correctly,
for my $exch (sort keys %spec_hash) {
for my $sym (sort keys %{ $spec_hash{$exch} }) {
print "Exchange: $exch, Symbol: $sym\n";
}
}
You want to loop over every symbol, but they are grouped by exchange, so you must first loop over the exchanges.
Data::Dumper doesn't sort its output by default.
Try adding $Data::Dumper::Sortkeys = 1; to your script.
use strict;
use warnings;
use Data::Dumper;
my %hash = (
'ARCX' => { 'IACI' => 1, 'MCHP' => 1, },
'AMXO' => { 'YUM' => 1, 'SYK' => 1, },
'XISX' => { 'FCEL' => 1, 'GPS' => 1, },
);
print do {
local $Data::Dumper::Sortkeys = 1;
Dumper \%hash;
};
Outputs:
$VAR1 = {
'AMXO' => {
'SYK' => 1,
'YUM' => 1
},
'ARCX' => {
'IACI' => 1,
'MCHP' => 1
},
'XISX' => {
'FCEL' => 1,
'GPS' => 1
}
};
Note: This can be modified to include a sort subroutine that you define

Converting HoA to HoH with counting

Have this code:
use 5.020;
use warnings;
use Data::Dumper;
my %h = (
k1 => [qw(aa1 aa2 aa1)],
k2 => [qw(ab1 ab2 ab3)],
k3 => [qw(ac1 ac1 ac1)],
);
my %h2;
for my $k (keys %h) {
$h2{$k}{$_}++ for (#{$h{$k}});
}
say Dumper \%h2;
produces:
$VAR1 = {
'k1' => {
'aa2' => 1,
'aa1' => 2
},
'k3' => {
'ac1' => 3
},
'k2' => {
'ab1' => 1,
'ab3' => 1,
'ab2' => 1
}
};
Is possible to write the above code with "another way"? (e.g. simpler or more compact)?
Honestly, I don't like the number of times $h2{$k} is evaluated.
my %h2;
for my $k (keys %h) {
my $src = $h{$k};
my $dst = $h2{$k} = {};
++$dst->{$_} for #$src;
}
A subroutine can help make the intent more obvious. Maybe.
sub counts { my %c; ++$c{$_} for #_; \%c }
$h2{$_} = counts(#{ $h{$_} }) for keys %h;
That can be simplified if you do the change in-place.
sub counts { my %c; ++$c{$_} for #_; \%c }
$_ = counts(#$_) for values %h;

How do I sort hash of hashes by a sub key/value using perl?

I would like sort the following Hash. parentXX should be sort with the value of __displayorder, the Xtopic of a parent and Xprod of a topic should sort alphabeticly.
$VAR1 = {
'views' => {
'parent23' => {
'__displayorder' => 2,
'vtopic1' => {
'gprod1.1' => undef,
'aprod1.2' => undef,
},
'btopic2' => {
'tprod2.1' => undef,
'mprod2.2' => undef,
},
},
'parent98' => {
'__displayorder' => 1,
'atopic1' => {
'qprod1.1' => undef,
'jprod1.2' => undef,
},
'xtopic2' => {
'kprod2.1' => undef,
'fprod2.2' => undef,
}
}
}
}
You can't sort a hash. Can you make do with having the names of the views in the specific order?
my $views = $VAR1->{views};
my #sorted_view_keys = sort {
$views->{$a}{__displayorder} cmp $views->{$b}{__displayorder}
} keys(%$views);
Or maybe you want the sorted views?
my #sorted_views = map { $views->{$_} } #sorted_view_keys;
-or-
my #sorted_views = #$views[#sorted_view_keys];
As already mentioned, you can't sort a normal Perl hash. Perl hashes are unordered. But you can use the CPAN module Tie::IxHash to get an ordered hash. The lines below transform all the sub-hashes from your sample output into Tie::IxHash hashes and do some sorting (e.g. alphabetically or by display order):
use Tie::IxHash;
my $views = $VAR1->{views};
while(my($view_key, $view) = each %$views) {
while(my($topic, $prods) = each %$view) {
next if $topic =~ m{^__};
tie my %new_prods, 'Tie::IxHash', (map { ($_ => $prods->{$_}) } sort keys %$prods);
$view->{$topic} = \%new_prods;
}
tie my %new_view, 'Tie::IxHash', (map { ($_ => $view->{$_}) } sort keys %$view);
$views->{$view_key} = \%new_view;
}
tie my %new_views, 'Tie::IxHash', (map { ($_ => $views->{$_}) } sort { $views->{$a}->{__displayorder} <=> $views->{$b}->{__displayorder} } keys %$views);
$VAR1->{views} = \%new_views;

Perl adding Lines into a Multi-Dimensional Hash

Hello I want to split a Line and add the Values in to a multi dimensional Hash. This is how the Lines look like:
__DATA__
49839382;Test1;bgsae;npvxs
49839384;Test2;bgsae;npvxs
49839387;Test3;bgsae;npvxs
So what I am doing now is:
my %prefix = map { chomp; split ';' } <DATA>;
But now I can only access Test1 with:
print $prefix{"49839382"}
But how can I also add the bgsae to the Hash so I can access is with
$prefix{"49839382"}{"Test1"}
Thank you for your help.
What structure are you trying to build?
use Data::Dumper;
my %prefix = map { chomp (my #fields = split /;/); $fields[0] => { #fields[1 .. $#fields] } } <DATA>;
print Dumper \%prefix;
Output:
$VAR1 = {
'49839384' => {
'Test2' => 'bgsae',
'npvxs' => undef
},
'49839382' => {
'Test1' => 'bgsae',
'npvxs' => undef
},
'49839387' => {
'npvxs' => undef,
'Test3' => 'bgsae'
}
};
Or do you need a deeper hash?
my %prefix;
for (<DATA>) {
chomp;
my $ref = \%prefix;
for (split /;/) {
warn "[$_]";
$ref->{$_} = {};
$ref = $ref->{$_};
}
}
Returns:
$VAR1 = {
'49839384' => {
'Test2' => {
'bgsae' => {
'npvxs' => {}
}
}
},
'49839382' => {
'Test1' => {
'bgsae' => {
'npvxs' => {}
}
}
},
'49839387' => {
'Test3' => {
'bgsae' => {
'npvxs' => {}
}
}
}
};
I don't know what you need the data for, but at a guess you want something more like this.
It builds a hash of arrays, using the first field as the key for the data, and the remaining three in an array for the value. So you can access the test number as $data{'49839382'}[0] etc.
use strict;
use warnings;
my %data = map {
chomp;
my #fields = split /;/;
shift #fields => \#fields;
} <DATA>;
use Data::Dumper;
print Data::Dumper->Dump([\%data], ['*data']);
__DATA__
49839382;Test1;bgsae;npvxs
49839384;Test2;bgsae;npvxs
49839387;Test3;bgsae;npvxs
output
%data = (
'49839384' => [
'Test2',
'bgsae',
'npvxs'
],
'49839382' => [
'Test1',
'bgsae',
'npvxs'
],
'49839387' => [
'Test3',
'bgsae',
'npvxs'
]
);