How do I sort hash of hashes by a sub key/value using perl? - 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;

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

not able to access hash of hash of array values

I have written the following code in Perl. The code is reading a pdb file and getting some values. Ignore the top part of the code,where everything is working perfect.
Problem is in the sub-routine part, where I try to store arrays in the hash3 with model as key another key position
the array values can be accessed inside the if condition using this :
$hash3{$model}{$coordinates}[1].
but when I go out of all foreach loop and try to access the elements I only get one value.
Please look at the end foreach loop and tell me is it the wrong way to access the hash values.
The pdb file I am using can be downloaded from this link http://www.rcsb.org/pdb/download/downloadFile.do?fileFormat=pdb&compression=NO&structureId=1NZS
#!/usr/bin/perl
open(IN,$ARGV[0]);
my #phosphosites;
my $model=1;
my %hash3;
while(<IN>)
{
#findmod(#line);
#finddist;
#findfreq;
if((/^MODRES/) && (/PHOSPHO/))
{
#line=split;
push(#phosphosites, $line[2]);
#print "$line[4]";
}
foreach $elements (#phosphosites){
if(/^HETATM\s+\d+\s+CA\s+$i/)
{
#line1=split;
#print "$line1[5]";
#print "$line1[6] $line1[7] $line1[8]\n";
push(#phosphositesnum, $line1[5]);
}
}
$pos=$line1[5];
#findspatial(\#line,\#line1);
}
my #ori_data=removeDuplicates(#phosphositesnum);
sub removeDuplicates {
my %seen = ();
my #vals = ();
foreach my $i (#_) {
unless ($seen{$i}) {
push #vals, $i;
$seen{$i} = 1;
}
}
return #vals;
}
$a=(#phosphosites);
print "$a\n";
print "#phosphosites\n";
print "#ori_data\n";
close(IN);
open(IN1,$ARGV[0]);
my (#data)=<IN1>;
spatial(\#ori_data);
sub spatial {
my #spatial_array1=#{$_[0]};
foreach $coordinates(#spatial_array1)
{
$model=1;
{foreach $data1(#data){
if($data1=~ m/^HETATM\s+\d+\s+CA\s+[A-Z]*\s+[A-Z]*\s+$coordinates/)
{
#cordivals=split(/\s+/,$data1);
push #{ $sphash{$model} },[$cordivals[6], $cordivals[7], $cordivals[8]];
$hash3{$model}{$coordinates}= \#cordivals;
#print "$model $coordinates $hash3{$model}{$coordinates}[6] $hash3{$model}{$coordinates}[7] $hash3{$model}{$coordinates}[8]\n";
#print "$model $sphash{$model}[$i][0] $sphash{$model}[$i][1] $sphash{$model}[$i][2]\n";
}
elsif($data1=~ m/^ENDMDL/)
{
$model++;
}
#print "$model $coordinates $hash3{$model}{$coordinates}[6] $hash3{$model}{$coordinates}[7] $hash3{$model}{$coordinates}[8]\n";
}
}
}
#foreach $z1 (sort keys %hash3)
# {
# foreach $z2(#spatial_array1){
# print "$z1 $z2";
# print "$hash3{$z1}{$z2}[6]\n";
# print "$z2\n";
# }
# }
}
After using the Data::Dumper option it is giving me this kind of output
$VAR1 = {
'11' => {
'334' => [
'HETATM',
'115',
'CA',
'SEP',
'A',
'343',
'-0.201',
'-2.884',
'1.022',
'1.00',
'99.99',
'C'
],
'342' => $VAR1->{'11'}{'334'},
'338' => $VAR1->{'11'}{'334'},
'335' => $VAR1->{'11'}{'334'},
'340' => $VAR1->{'11'}{'334'},
'343' => $VAR1->{'11'}{'334'},
'336' => $VAR1->{'11'}{'334'}
},
'7' => {
'334' => $VAR1->{'11'}{'334'},
'342' => $VAR1->{'11'}{'334'},
'338' => $VAR1->{'11'}{'334'},
'335' => $VAR1->{'11'}{'334'},
'340' => $VAR1->{'11'}{'334'},
'343' => $VAR1->{'11'}{'334'},
'336' => $VAR1->{'11'}{'334'}
},
'2' => {
'334' => $VAR1->{'11'}{'334'},
'342' => $VAR1->{'11'}{'334'},
...
Change:
#cordivals=split(/\s+/,$data1);
to:
my #cordivals=split(/\s+/,$data1);
What seems to be happening is that all the hash elements contain references to the same array variable, because you're not making the variable local to that iteration.
In general, you should use my with all variables.

Perl accessing a hash within a hash and looping through each to extract the value

I am trying to access a hash within a hash and loop through it to get the values. here is an example of the data
$VAR1 = {
'24.40.53.143' => {
'ServStat' => {
'1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115' => 'vs_cgggbpm-ap_https',
'1.17.118.115.95.99.119.98.115.102.97.45.97.112.95.104.116.116.112' => 'vs_cddedsfa-ap_http',
'20.18.118.115.95.99.119.98.116.119.98.45.98.112.95.104.116.116.112.115' => '0',
'19.17.118.115.95.99.119.98.119.115.45.97.112.95.104.116.116.112.115' => '0',
'2.18.118.115.95.99.119.98.116.119.98.45.98.112.95.104.116.116.112.115' => '0',
'24.18.118.115.95.99.119.98.116.119.98.45.97.112.95.104.116.116.112.115' => '0',
'17.17.118.115.95.99.119.98.119.98.45.97.112.95.104.116.116.112.115' => '0',
'29.17.118.115.95.99.119.98.116.119.112.45.98.112.95.104.116.116.112' => '0',
I would like to loop through 'ServStat' and extract each values. How would I reference the hash 'ServStat' so that I can do a foreach on the contents? Something like this:
foreach {ServStat} {
my ( $num, $char, $vs ) = (/(\d+)\.(\d+)\.(.+)/ );
if ($num == 1) {
print {ServStat}->$value
}
}
Thank you in advance for any advise you can offer!
To get the keys, you can use the function keys on the hash.
my $data = {
'24.40.53.143' => {
'ServStat' => {'1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115' => 'vs_cgggbpm-ap_https'}
}
};
my $ServStat = $data->{24.40.53.143}{ServStat};
foreach my $key (keys %{$ServStat}) { # you need the {} to dereference as $ServStat is a hash reference
...#Now, in $key, you have the key 1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115
}
If you just want all values, just use the function values on the hash
my #values = values %{$ServStat};

How can I convert one Perl hash to another using the keys?

I've just started diving in to the crazy world that is perl and have come across a problem that I cannot seem to wrap my head around. Specifically I need to be able to convert from one hash structure to another, that uses the keys/values as the new hashes keys/values.
An example:
Input hash:
my %original_hash = (
first_key => { some_key => "apples",
another_key => "chips",
#potentially more here
},
second_key => { more_of_same => "dogs",
its_another => "cats",
#potentially more here
}
);
Output hash:
my %final_hash = (
some_key => {
apples => {
more_of_same => "dogs",
its_another => "cats",
}
} ,
another_key => {
chips => {
more_of_same => "dogs",
its_another => "cats",
}
}
);
And yes I do want the second_key's data repeated in the final_hash, as there will be an array of the original_hashes that are inputted. The first element becomes the base-case, and all other elements may append or remove from that list.
If anyone has any suggestions on how to go about doing this that would be greatly appreciated!
Here is another way
my %final_hash;
my %tmp = %{$original_hash{first_key}};
my $val = $original_hash{second_key};
while ( my ($k,$v) = each %tmp) {
$final_hash{$k} = { $v => $val };
}
print Dumper (\%final_hash);
Okay, Sinan is right, it's very hard to guess your problem, but the following code seems to do what you want ... or at least it produces the listed output.... :)
use strict;
use warnings;
use Data::Dumper;
$Data::Dumper::Deepcopy = 1;
my %original_hash = (
first_key => { some_key => "apples",
another_key => "chips",
#potentially more here
},
second_key => { more_of_same => "dogs",
its_another => "cats",
#potentially more here
}
);
my %final_hash;
for my $key ( keys %{ $original_hash{first_key} } ) {
$final_hash{$key} = {
$original_hash{first_key}->{$key}
=> $original_hash{second_key}
};
}
print Dumper(\%final_hash);