Traversing a hash in order - perl

I would like to traverse the HASH but one by one. Not in Random ways. Any idea. For example i have hash file something like this...
our %HASH = (
'rajesh:1700' => Bangalore,
'rajesh:1730' => Delhi,
'rajesh:1770' => Ranchi,
'rajesh:1780' => Mumbai,
'rajesh:1800' => MYCITY,
'rajesh:1810' => XCF,
);
and it should print in same fashion. I tried with following but failed. Any ideas?
while ( my $gPort = each %HASH)
{
print "$gPort\n";
}
for my $gPort ( keys %HASH )
{
print "$gPort\n";
}

Given the keys in your question, a simple change to the sort comparator will give your desired output.
for my $gPort (sort keys %HASH) {
print "$gPort => $HASH{$gPort}\n";
}
Note: the code above assumes all numbers in keys will occur at the same position and have the same length. For instance, a rajesh:001775 key will come out first rather than between 1770 and 1780.

You could sort and print out a hash, ordering by VALUE (not keys).
for my $gPort (sort { $HASH{$a} <=> $HASH{$b} } keys %HASH) {
print "$gPort => $HASH{$gPort}\n";
}

Take a look at Data::Dumper. In particular, if you set $Data::Dumper::Sortkeys, then you would get the dump in sorted order.
As an example:
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
my %some_hash;
# code to populate hash
[ . . . ]
print Dumper(\%some_hash);
Of course, this would work only if you want to plainly dump the hash. If you want the printing to be done in some other format, you would want to just sort the keys and print, like
foreach my $key (sort keys %some_hash) {
print "[KEY]: $key; [VAL]: $some_hash{$key}\n";
}

If you wish to preserve the insert-order of your elements in your hash then Tie::IxHash may be the tool for you. It's usage is very simple:
Showing you simple example:
use Tie::IxHash;
tie my %days_in => 'Tie::IxHash',
January => 31,
February => 28,
March => 31,
April => 30,
May => 31,
June => 30,
July => 31,
August => 31,
September => 30,
October => 31,
November => 30,
December => 31;
print join(" ", keys %days_in), "\n";
# prints: January February March April May June July August
# September October November December

Related

Can't modify key/value hash slice in list assignment at .\metro.pl line 31, near "$year;"

I'm new in Perl. I can not understand why I can not assign year to the hash field in string %currentBook{year}=$year;
Full code here.
use warnings;
use strict;
use Scalar::Util qw(looks_like_number);
use Time::localtime;
my $maxYear = Time::localtime->year+1;
my $year = $maxYear;
my %currentBook = (name=>"firstCurrentBook",
author=>"NO",
place=>"NO",
year=>0);
my %maxBook = %currentBook;
my %minBook = %currentBook;
print "Choose action\n1 - Input book\n2 - Print min max year\n3 = exit\n->";
my $cond = <STDIN>;
while ($cond != 3)
{
if ($cond == 1){
print "\nInput book name: ";
$currentBook{name} = <STDIN>;
print "\nInput author surname and initials: ";
$currentBook{author} = <STDIN>;
print "\nInput place: ";
$currentBook{place} = <STDIN>;
do{
print "\nInput year of book: ";
$year = <STDIN>;
chomp $year;
} while (!looks_like_number($year) || $year < 0 || $year > Time::localtime->year);
%currentBook{year}=$year;
if (%currentBook{year} > %maxBook{year}){
%maxBook=%currentBook;
}
if (%currentBook{year} < %minBook{year}){
%minBook=%currentBook;
}
}
}
You've already got the answer in a comment by toolic, but I will elaborate why this happens.
my %books = ( year => 2017 );
%books{year} = 2018;
This code will throw the error you saw.
Can't modify key/value hash slice in list assignment at
/home/simbabque/code/scratch.pl line 6313, near "2018;"
Execution of /home/simbabque/code/scratch.pl aborted due to compilation errors.
To make your program do what you intended, you need to use the $ sigil, not the % sigil, because the value inside $books{year} is a scalar.
But why the error message?
Actually, %books{year} is a totally valid Perl expression.
use Data::Dumper;
my %books = ( year => 2017 );
print Dumper %books{year};
This will print
$VAR1 = 'year';
$VAR2 = 2017;
The construct %book{year} is a so-called hash slice, and it returns a list of key/value pairs. You can also put in a list of keys, and get a list of both the keys and their values out. This is useful to quickly construct a sub hash.
my %timestamp = ( year => 2017, month => 12, day => 31, hour => 23, minute => 59 );
my %date = %timestamp{ 'year', 'month', 'day' };
print Dumper \%date;
The output of that is
$VAR1 = {
'day' => 31,
'month' => 12,
'year' => 2017
};
However, this kind of behavior does not allow you to assign to %books{year}. It simply does not make sense, because it returns a list of key/value pairs. That's why this construct is not what we call an lvalue in Perl, so it cannot be on the left hand side of an assignment.

Sort both levels of keys for hash of hashes in perl

I have a code where I need to keep track of some values (that come up at random) at given positions in different categories (and a fairly large number of them; ~40,000), so I thought a hash of hashes would be the best way, with categories as first layer of keys, position as second and values as values; something like:
%HoH = {
'cat1' => {
'7010' => 19,
'6490' => 13,
'11980' => 2
}
'cat2' => {
'7010' => 28,
'10470' => 13,
'205980' => 54
}
}
Then I need to sort and print them in order of both categories and then position, to get an output file like:
cat1 6490 13
cat1 7010 19
...
cat2 7010 28
But I can't work out the syntax for the nested sorting (alternatively, anyone got a better idea than this approach?)
Perl makes it easy to efficiently sort the keys while iterating through a hash of hashes:
for my $cat (sort keys %HoH) {
# numerical sort:
for my $digits (sort { $a <=> $b } keys %{$HoH{$cat}}) {
print join("\t", $cat, $digits, $HoH{$cat}{$digits}) . "\n";
}
}

Minimum and maximum values for Perl hash of hashes

This is a variation from another question asked on perlmonks and is similar to the problem I'm trying to figure out. I have the following hash of hashes.
%Year = (
2007 => {
ID1 => 07,
ID4 => 34,
ID2 => 24,
ID9 => 14,
ID3 => 05,
},
2008 => {
ID7 => 11,
ID9 => 64,
ID10 => 20,
ID5 => 13,
ID8 => 22,
}
)
I would like to find the two smallest and two largest values together with their corresponding IDs for each year. Can this be done using List::Util qw (min max)?
Desired results:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID10,20 min1:ID7,11 min2:ID5,13
Unless the lists are huge, it is probably best to find the two largest and two smallest hash values just by sorting the entire hash and picking the first two and last two elements.
You seem to have incorrect expectations for your output. For 2008 the hash data sorted by value looks like
ID7 => 11
ID5 => 13
ID10 => 20
ID8 => 22
ID9 => 64
so max1 and max2 are ID9 and ID8, while min1 and min2 are are ID7 and ID5. But your question says that you expect max2 to be ID10, whose value is 20 - right in the middle of the sorted range. I think max2 should be ID8 which has a value of 22 - the second largest value in the 2008 hash.
I suggest this solution to produce the output that I think you want
use strict;
use warnings;
use 5.010;
my %year = (
2007 => { ID1 => 7, ID2 => 24, ID3 => 5, ID4 => 34, ID9 => 14 },
2008 => { ID10 => 20, ID5 => 13, ID7 => 11, ID8 => 22, ID9 => 64 },
);
for my $year (sort { $a <=> $b } keys %year) {
my $data = $year{$year};
my #sorted_keys = sort { $data->{$a} <=> $data->{$b} } keys %$data;
printf "%4d - max1:%s,%02d max2:%s,%02d min1:%s,%02d min2:%s,%02d\n",
$year, map { $_ => $data->{$_} } #sorted_keys[-1,-2,0,1];
}
output
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11 min2:ID5,13
TIMTOWDI: You've mentioned hash of hash, so you can sort your inner hash by values and take a slice (that is first two and last two elements).
#!/usr/bin/perl
use strict;
use warnings;
my %Year = (
2007 => { ID1 => 7, ID2 => 24, ID3 => 5, ID4 => 34, ID9 => 14 },
2008 => { ID10 => 20, ID5 => 13, ID7 => 11, ID8 => 22, ID9 => 64 },
);
for my $year (keys %Year) {
printf "%4d - max1:%s,%02d max2:%s,%02d min1:%s,%02d min2:%s,%02d\n",
$year,
map { $_, $Year{$year}{$_} }
( sort { $Year{$year}{$b} <=> $Year{$year}{$a} } keys %{$Year{$year}} )[0,1,-1,-2];
}
Output:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11 min2:ID5,13
You have hashes, and List::Util works on lists/arrays. That disqualifies you right there since both the keys and the data are still important for you.
It's possible to create a second hash that's keyed by the data, then I could use something from List::Util or List::MoreUtils on that to pull up the data you want, and then look up the keys for that data. However, that's a lot of work just to get the information you want.
In reality, you're not sorting the hash of hashes, but just the data in each year. This makes the job a lot easier.
Normally, when you sort a hash, you're sorting on the keys. However, you can specify a subroutine inside the sort command to change the way Perl sorts. Perl will hand you two items $a and $b which represents the keys to your hash. You figure out which is the bigger one, and pass that back to Perl. Perl gives you <=> for numbers and cmp for non-numeric data.
All I have to do is specify sort { $array{$a} cmp $array{$b} } keys %array to sort by the data and not the keys. I simply toss the sorted keys into another array, then use index positioning to pull out the data I want.
#! /usr/bin/env perl
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
my %year;
#
# Data
#
$year{2007}->{ID1} = "07";
$year{2007}->{ID2} = "24";
$year{2007}->{ID3} = "05";
$year{2007}->{ID4} = "34";
$year{2007}->{ID9} = "14";
$year{2008}->{ID7} = "11";
$year{2008}->{ID9} = "64";
$year{2008}->{ID10} = "20";
$year{2008}->{ID5} = "13";
$year{2008}->{ID8} = "22";
#
# For Each Year...
#
for my $year ( sort keys %year ) {
print "$year - ";
#
# No need to do this dereferencing, but it makes the rest of the code cleaner
#
my %id_hash = %{ $year{$year} };
#
# Now I sort my IDs by their data and not the key names
#
my #keys = sort { $id_hash{$a} cmp $id_hash{$b} } keys %id_hash;
#
# And print them out
#
print "max1:$keys[-1],$id_hash{$keys[-1]} ";
print "max2:$keys[-2],$id_hash{$keys[-2]} ";
print "min1:$keys[0],$id_hash{$keys[0]}, ";
print "min2:$keys[1],$id_hash{$keys[1]}\n";
}
The output is:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05, min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11, min2:ID5,13

Perl hash of hashes and array - print the array contents

Have the below data structure. I want to print the entire array for the key TUESDAY. Tried the below way but it's not working. I don't want to have an additional statement of taking the array reference to a variable and printing out later. I want to do it in single statement in the print function.
my $FILE_LIMIT = {
CHECK => "ON",
ISANE => {
CHECK => "ON",
MONDAY => 33,
TUESDAY => [10, 20, 30, 40],
WEDNESDAY => 12,
THURSDAY => 13,
SATURDAY => 14,
SUNDAY => 15
} };
print "array val: " . $FILE_LIMIT->{ISBANE}->{TUESDAY}[1 .. $#] . "\n";
print "array val: " . join (' ', #{ $FILE_LIMIT->{ISANE}->{TUESDAY} }), "\n";
As you have guessed, the array reference is at $FILE_LIMIT->{ISANE}{TUESDAY}, so dereference it using
print "array val: #{$FILE_LIMIT->{ISANE}{TUESDAY}}\n";
output
array val: 10 20 30 40
print" #{ $FILE_LIMIT->{ISANE}->{TUESDAY}}\n";
output:
10 20 30 40
you are basically trying to take out the array slice in your code, that is useful if you want to take out only specific elements of the array, try to understand the below code that would take out the some elements from the array.
my #test = #{ $FILE_LIMIT->{ISANE}->{TUESDAY}};
print " #{$FILE_LIMIT->{ISANE}->{TUESDAY}}[1 .. $#test] \n";
output:
20 30 40
my $FILE_LIMIT = {
CHECK => "ON",
ISANE => {
CHECK => "ON",
MONDAY => 33,
TUESDAY => [10, 20, 30, 40],
WEDNESDAY => 12,
THURSDAY => 13,
SATURDAY => 14,
SUNDAY => 15
} };
print $_,"\n",foreach(#{$FILE_LIMIT->{ISANE}->{TUESDAY}});

sorting hash values in perl [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Perl sorting hash by values in the hash
I have browsed the web quite a bit for a solution, but I couldn't find the anything that meets my needs.
I have a large list of words with values attached to each word
Example:
my %list = (
word => 10,
xword => 15,
yword => 1
)
The list goes on and on, but I want to be able to return the top 5 hash elements with the highest corresponding values
use strict;
use warnings;
sub topN {
my ($N, %list) = (shift, #_);
$N = keys %list if $N > keys %list;
return (sort { $list{$b} <=> $list{$a} } keys %list)[0..$N-1];
}
my %list = ( word => 10, xword => 15, yword => 1, zword => 4);
print join (",", topN(5, %list)), "\n";
Output:
xword,word,zword,yword
This does what you need. Note that it will throw Use of uninitialized value warnings if your hash has fewer than five elements and you may have to add code to cater for that. It is also inefficient in that it sorts the entire hash rather than finding only the top five values. Whether or not that is an issue depends on your circumstance.
use strict;
use warnings;
my %list = (
word => 10,
xword => 15,
yword => 1,
);
my #top5 = (sort { $list{$b} <=> $list{$a} } keys %list)[0..4];
print "$_\n" for #top5;
output
xword
word
yword
use strict;
use warnings;
my %list = (
word => 10,
xword => 15,
yword => 1,
);
my #top5 = sort { $list{$b} <=> $list{$a} } keys %list;
splice(#top5, 5) if #top5 > 5;
print "$_\n" for #top5;