Perl hash of hashes and array - print the array contents - perl

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

Related

Perl; how to filter an hash by value (specifying a condition)

I'm not very expert in perl language but I encountered a problem that I couldn't fix, even after a long research on the web.
Briefly, I have an hash of hashes like this:
my %HoH = (
chr1 => { start => 30, end => 55, },
chr1 => { start => 18, end => 21, },
chr1 => { start => 30, end => 80, }
);
I simply would like to find a way to filter it ( I mean, obtaining a new hash of hashes in output) for particular values. In particular, given an interval, let's say 40-60, I want a new hash of hashes with only elements overlapping this interval.
in other words I would like to get as output:
my %HoH = (
chr1 => { start => 30, end => 55, },
chr1 => { start => 30, end => 80, }
);
As first attempt, I thought to try something like this:
identify and then delete all elements with "end" < 40 and:
identify and then delete all elements with "start" > 60.
So I just tried:
grep { $HoH{$_}{"end"} < 40 } keys(%HoH);
delete $HoH{$_} for grep { $HoH{$_}{"end"} < 40} keys(%HoH);
But just after the first of the two filters I found in the output only last element and I really don't understand where is the mistake:
hash size is 1
chr1: start=30 end=80
printed out with the following:
my $len = keys %HoH;
print "hash size is $len\n";
foreach my $chr ( keys %HoH ) {
print "$chr: ";
for my $position ( keys %{ $HoH{$chr} } ) {
print "$position=$HoH{$chr}{$position} ";
}
print "\n";
}
It seems quite complex for me this time, I would be glad if somebody of you could give me some help.
As another poster mentions - your problems isn't your hash merge, it's that hashes cannot have duplicate keys:
use strict;
use warnings;
use Data::Dumper;
my %HoH = (
chr1 => { start => 30, end => 55, },
chr2 => { start => 18, end => 21, },
chr3 => { start => 30, end => 80, }
);
grep { $HoH{$_}{"end"} < 40 } keys(%HoH);
delete $HoH{$_} for grep { $HoH{$_}{"end"} < 40} keys(%HoH);
print Dumper \%HoH;
This works correctly - note the different hash keys. I would note though - you're iterating your keys, grepping them, then deleting them. It might be better to:
foreach my $element ( keys %HoH ) {
delete $HoH{$element}
unless ( $HoH{$element}{start} < 40
or $HoH{$element}{end} > 60 );
}
print Dumper \%HoH;
You could do what you're trying to do via an array of hashes:
use strict;
use warnings;
use Data::Dumper;
my #AoH = (
{ start => 30, end => 55, },
{ start => 18, end => 21, },
{ start => 30, end => 80, }
);
print Dumper \#AoH;
my #filtered = grep { $_->{start} > 40 or $_->{end} < 60 } #AoH;
print Dumper \#filtered;
Note - in your original example, your grep/delete lines are doing the same thing, and you can do a compound grep to test for both conditions.
Inspect your hash using Data::Dumper and you'll see that you don't have the data structure you thought you did:
use strict;
use warnings;
use Data::Dumper;
my %HoH = (
chr1 => {
start => 30,
end => 55,
},
chr1 => {
start => 18,
end => 21,
},
chr1 => {
start => 30,
end => 80,
},
);
print Dumper \%HoH;
$VAR1 = {
'chr1' => {
'start' => 30,
'end' => 80
}
};
What's happening is that it is taking that last unique entry for chr1. Hash keys must be unique

DateTime::subtract_datetime returns zero for difference in hours

Here is a minimal working example:
#!/usr/bin/perl
use strict;
use warnings;
use DateTime;
my $now = DateTime->new(day => 17,
month=> 10,
year => 2014,);
my $date = DateTime->new(day => 3,
month=> 10,
year => 2014,
);
my $dur = $now->subtract_datetime($date);
print 'hours = ', $dur->hours(), "\n";
When I run it on my server I get this:
hours = 0
It's incorrect. The correct answer is 14 days = 336 hours.
What's wrong with the code?
Not all days have 24 hours, so 14 days is not always 336 hours. If you want the difference in hours, you'll have to specifically ask for that (or in something that can be converted to hours such as minutes). delta_ms is the method to do that.
my $now = DateTime->new(day => 17,
month=> 10,
year => 2014,);
my $date = DateTime->new(day => 3,
month=> 10,
year => 2014,
);
my $dur = $now->delta_ms($date);
print 'hours = ', $dur->in_units('hours'), "\n";
Which returns what you want.
See How to make DateTime::Duration output only in days? for more discussion.

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

Mapping a bunch of hashes in Perl, but its skipping a single value in the mapping?

This will be a lot easier to show in code than explain. I've just got a hash mapping to quickly fill a hash to run replacements on data the script reads in. E.g. if 5 output 6, if 3 output 2, if 23 output 6, etc etc. This works pretty well except for not mapping one of the keys.
(btw, if anyone can think of a more elegant way to map several keys in a hash to a single value, let me know :) )
Anyways onto the code...
$COLUMN = 6;
%PERIOD_1 = (map {( 1, 10, 14, 20, 22, 29, 35, 39 )[$_] => 1 } 0..100); #1st period
%PERIOD_2 = (map {( 3, 8, 11, 18, 24, 26, 32, 37 )[$_] => 2 } 0..100); #2nd period
%PERIOD_3 = (map {( 7, 13, 16, 21, 28, 34, 36 )[$_] => 3 } 0..100); #3rd period
%PERIOD_4 = (map {( 5, 2, 6, 15, 17, 23, 27, 31,38)[$_] => 4 } 0..100); #4th period
%PERIOD_5 = (map {( 4, 9, 12, 19, 25, 30, 33, 40 )[$_] => 5 } 0..100); #5th period
%PERIODS = (%PERIOD_1,%PERIOD_2,%PERIOD_3,%PERIOD_4,%PERIOD_5);
open (FILE,"<",$ARGV[0]);
while(<FILE>) {
my #columns = split(/\t/);
print $columns[$COLUMN] . "-" . $PERIODS{$columns[$COLUMN]};
}
close(FILE);
So this works pretty well. You get an output like:
37-2
29-1
15-4
6-4
34-3
24-2
5-
Which matches/replaces literally every value EXCEPT 5. I don't get it - all of the other keys in the mapping are in the hash, but for some reason 5 (and only 5) isn't. Could someone explain wtf the problem is? edit: fixed formatting
EDIT: YES, I USED WARNINGS IN THE CODE. I really don't care about the warning though (even if it is related to my problem) - I just want the problem solved. If I cared to understand the warning I would've asked about it.
This is some rather unusual code, and its probably not doing what you think. For example:
%PERIOD_2 = (map {( 3, 8, 11, 18, 24, 26, 32, 37 )[$_] => 2 } 0..100);
This will iterate over numbers from 0 to 100, but only 0 to 7 are of any interest to you, since the list contains 8 numbers. The subscript for 8, for example, will be empty, and the map iteration will return () => 2, or just a single 2. This will mean that you will get a long string of 2,2,2,2,2,2,2, which will lead to the key 2 always existing in that hash, no matter if it is in the list or not.
This is an overly complicated way of turning a list into a hash. You would normally just do:
my %hash = map { $_ => 2 } ( 3, 8, 11, 18, 24, 26, 32, 37 );
And if you had several lists to merge into one hash, you would do
my %hash;
for my $num (1, 10, 14, 20, 22, 29, 35, 39) {
$hash{$num} = 1;
}
for my $num (3, 8, 11, 18, 24, 26, 32, 37) {
$hash{$num} = 2;
}
....
If you had used
use strict;
use warnings;
You would most likely not have this problem, because you would get the warning Odd number of elements in hash assignment as toolic pointed out.
Also, in cases such as these, using the Data::Dumper module to debug is very convenient:
use Data::Dumper;
print Dumper \%hash; # send ref to hashes and arrays
For the code you had above, with these debugging tools you would get this output:
Odd number of elements in hash assignment at foo.pl line 9.
$VAR1 = {
'32' => 2,
'11' => 2,
'3' => 2,
'26' => 2,
'2' => undef,
'8' => 2,
'18' => 2,
'24' => 2,
'37' => 2
};
And as you can see, the problem is immediately identifiable: The key 2 has no value.
When you add the hashes together, you also overwrite previous valid values, such as the one for 5. For example:
%hash1 = (5 => 2);
%hash2 = (5 => undef);
%hash = (%hash1, %hash2);
Since hash keys are unique, 5 => undef will overwrite 5 => 2.
The "Odd number of elements" warning refers to the list that is assigned to the hash, not the number of keys. For example:
my %foo = (1, 2, 3); # odd number of elements
This hash will now have the keys 1 and 3, but key 3 will not have a value, so it will be undef. Usually in hash assignment, you want an even number of elements, so that each key has a value.
You should always
use strict;
use warnings;
at the top of your programs, and declare every variable with my as close as possible to its first point of use.
You should also preferably use only lower case and underscore for your variable names. Upper case is reserved for global identifiers like package names.
It is best to use a lexical file handle instead of a global one, and you should always check the success of your open calls, dying with the value of $! in the die string. Like this
open my $file, '<', $ARGV[0] or die "Unable to open input file: $!";
What this will do
my %period = (map {( 1, 10, 14, 20, 22, 29, 35, 39 )[$_] => 1 } 0..100);
is correctly generate your key/value pairs for those elements in the list, but when you get to 8 (the size of the list) it will just produce the value 1 as there is no corresponding element of the list. So you get
1 => 1,
10 => 1,
14 => 1,
20 => 1,
22 => 1,
29 => 1,
35 => 1,
39 => 1,
1,
1,
1,
1,
1,
and for those hashes where there is an even number of elements in the list you will end up with an unpaired trailing value.
I suggest you put the information into a data file, or at least into the __DATA__ section of your program, and read it into the hash like this
use strict;
use warnings;
my $column = 6;
my %periods;
while (<DATA>) {
my ($val, #keys) = /\d+/g;
next unless $val;
$periods{$_} = $val for #keys;
}
open my $fh, "<", $ARGV[0];
while(<$fh>) {
my #columns = split /\t/;
my $key = $columns[$column];
printf "%s - %s\n", $key, $periods{$key};
}
__DATA__
1: 1, 10, 14, 20, 22, 29, 35, 39;
2: 3, 8, 11, 18, 24, 26, 32, 37;
3: 7, 13, 16, 21, 28, 34, 36;
4: 5, 2, 6, 15, 17, 23, 27, 31, 38;
5: 4, 9, 12, 19, 25, 30, 33, 40;
(Don't worry about the format of the lines after __DATA__. It takes notice only of the decimal digits in the line. Everything else is just layout and is ignored.)

Traversing a hash in order

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