Loop through multidimensional Hash of Arrays in Perl - arrays

I have a multidimensional hash of arrays that represent a student's grade in each subject for the first four assignments.
my %students_grades = (
Colton => {
English => [ 90, 95, 80, 75 ],
Mathematics => [ 77, 89,94, 100 ],
},
);
The syntax is a bit off but here's the code that creates the hash of arrays above.
#!/usr/bin/perl
my %students_grades;
$students_grades{'Colton'}{'English'}[0] = 90;
$students_grades{'Colton'}{'English'}[1] = 95;
$students_grades{'Colton'}{'English'}[2] = 80;
$students_grades{'Colton'}{'English'}[3] = 75;
$students_grades{'Colton'}{'History'}[0] = 77;
$students_grades{'Colton'}{'History'}[1] = 89;
$students_grades{'Colton'}{'History'}[2] = 94;
$students_grades{'Colton'}{'History'}[3] = 100;
How do I loop through the student's grades he received in History using a foreach loop? Right now I'm looping through it using a for loop.
my $num_of_grades = scalar #{$students_grades{'Colton'}{'History'}};
for (my $i=0; $i <= $num_of_grades; $i++) {
print $students_grades{'Colton'}{'History'}[$i] . "\n";
}
This is a representation of my code but in my actual program my hash of arrays is more complicated so I want to loop through the hash of arrays using a foreach loop because it'll be easier to handle. How do I do that?

for (my $i=0; $i<#array; ++$i) {
my $ele = $array[$i];
...
}
can be written simply as
for my $ele (#array) {
...
}
so you could have used the following:
for my $grade (#{ $students_grades{'Colton'}{'English'} }) {
print("$grade\n");
}
my $grade = $students_grades{'Colton'}{'English'}[0];
is short for
my $grade = $students_grades{'Colton'}->{'English'}->[0];
which means you can do
my $grades = $students_grades{'Colton'}{'English'};
my $grade = $grades->[0];
which means you could also have used the following:
my $grades = $students_grades{'Colton'}{'English'};
for my $grade (#$grades) {
print("$grade\n");
}
Knowing this allows one to easily escalate to the following:
for my $student_name (keys(%students_grades)) {
my $student_grades_by_class = $students_grades{$student_name};
for my $class_name (keys(%$student_grades_by_class)) {
my $grades = $student_grades_by_class->{$class_name};
for my $grade (#$grades) {
print("$student_name: $class_name: $grade\n");
}
}
}

In perl, for and foreach mean the same.
But the thing here is - you don't have a hash of arrays - you have a hash of array references.
So:
$students_grades{'Colton'}{'History'}
is actually an array reference.
So you can dereference it:
foreach my $grade ( #{ $students_grades{'Colton'}{'History'} } ) {
print "$grade\n";
}

Related

Perl Hash : get keys if the value is in-between a range

I have a two dimensional hash with 100K keys(primary) like this and I need to get the primary key - the name of the fruit only if a particular condition is satisfied;
like - if the price is between 35 and 55; Desired output is Orange and Grape.
And there is a list (hundreds in count) of unique price ranges for which I need a list of fruits within each range.
Iterating through hash again and again for each price range takes a lot of time.
Is there a way we can do it quickly instead of looping through the entire hash for each price range?
Hash format :
$fruits{"Mango"}{color}=Yellow
$fruits{"Mango"}{price}=80
$fruits{"Orange"}{color}=Orange
$fruits{"Orange"}{price}=40
$fruits{"Grape"}{color}=Green
$fruits{"Grape"}{price}=50
Here is an example of how you can do a single scan through the fruits by sorting the the prices in numerical order. This should be faster than scanning the whole hash once for each price range:
package Main;
use v5.20.0;
use feature qw(say);
use strict;
use warnings;
use experimental qw(signatures);
{
my %fruits;
$fruits{Mango}{color} = "Yellow";
$fruits{Mango}{price} = 80;
$fruits{Orange}{color} = "Orange";
$fruits{Orange}{price} = 40;
$fruits{Grape}{color} = "Green";
$fruits{Grape}{price} = 50;
my #ranges = ( [35, 55], [45, 55], [2, 85] );
my $self = Main->new(
fruits => \%fruits,
ranges => \#ranges
);
$self->init_mapping_arrays();
my $names = $self->get_fruit_names_for_all_ranges();
}
sub init_mapping_arrays( $self ) {
my #prices;
my #names;
for my $fruit (keys %{ $self->{fruits} }) {
push #names, $fruit;
push #prices, $self->{fruits}{$fruit}{price};
}
my #idx = map { $_->[0] }
sort { $a->[1] <=> $b->[1] } map { [$_, $prices[$_]] } 0..$#prices;
$self->{prices} = [#prices[#idx]];
$self->{names} = [#names[#idx]];
}
sub get_fruit_names_for_all_ranges ($self) {
my #names;
my $prices = $self->{prices};
my $ranges = $self->{ranges};
for my $i (0..$#$prices) {
for my $range (0..$#$ranges) {
if ( ($ranges->[$range][0] <= $prices->[$i])
&& ($ranges->[$range][1] >= $prices->[$i]))
{
push #{$names[$range]}, $self->{names}[$i];
}
}
}
return \#names;
}
sub new( $class, %args ) { bless \%args, $class }
If this is not fast enough, the get_fruit_names_for_all_ranges() sub can be optimized further by also sorting the ranges.
If the fruits are sorted, two binary searches would find the fruits quickly.
sub search_cmp
my #fruits = (
{ name => "Orange", price => 40, ... },
...
);
my #ranges = (
[ 35, 55 ],
...
);
my #sorted_fruits = sort { $a->{price} <=> $b->{price} } #fruits;
for my $range (#ranges) {
my $i = binsearch { $a <=> $b->{price} } $range[0], #sorted_fruits, 0;
$i = ~$i if $i < 0;
my $j = binsearch { $a <=> $b->{price} } $range[1], #sorted_fruits, $i;
$j = ~$j - 1 if $j < 0;
say "[$range->{min}, $range->{max}]: #fruits[$i..$j]";
}
sub _unsigned_to_signed { unpack('j', pack('J', $_[0])) }
sub binsearch(&$\#;$$) {
my $compare = $_[0];
#my $value = $_[1];
my $array = $_[2];
my $min = $_[3] // 0;
my $max = $_[4] // $#$array;
return -1 if $max == -1;
my $ap = do { no strict 'refs'; \*{caller().'::a'} }; local *$ap;
my $bp = do { no strict 'refs'; \*{caller().'::b'} }; local *$bp;
*$ap = \($_[1]);
while ($min <= $max) {
my $mid = int(($min+$max)/2);
*$bp = \($array->[$mid]);
my $cmp = $compare->()
or return $mid;
if ($cmp < 0) {
$max = $mid - 1;
} else {
$min = $mid + 1;
}
}
return _unsigned_to_signed(~$min);
}
Performance analysis
The best possible worst case is O(R * F) because every range could match every fruit.
The naive approach described by the OP asked to replace is O(R * F). So is it as fast as it can be? No, because the naive approach always adopts its worse case.
In practice, if we can assume that each range matches just a few fruits, we can get far far better results on average from the above: O( ( F + R ) log F )

convert hash to array of hashes in Perl

How simply convert a hash to an array of key/value ?
my %h;
%h{1} = 11;
%h{2} = 22;
and I want an array #result which I could represent as : [ { 1 => 11 }, { 2 => 22} ] (yes like in json to be clear)
That's an easy one.
my #h = map { { $_ => $h{$_} } } keys %h;
You could also use the built in List::Util library's excellent pairmap function.
use List::Util qw< pairmap >;
my #array_of_pairs = pairmap { { $a => $b } } %some_hash;
Even if you don't use this function today, take some time to check out List::Util, there is a lot of good stuff in there.

Accessing array of hashes in Perl

I have problem with accessing a hash in each element of array after creating it but it gave the last element. What should I do to access all the elements of my array?
When I want push a hash to an array I use {} instead of () because if I don't it gave me error. How does it see when I use {}?
#stem = ();
for ($i = 0; $i < 2; ++$i) {
push #stem, { u1 => 1, u2 => 2 , u3 => 3 };
}
#ants = ();
$count = 0;
for ($i = 0; $i < scalar(#stem); ++$i) {
#allowed = ();
%hash = ();
for ($j = 0; $j < scalar(#stem); ++$j) {
push #allowed, { stem => ++$count, hinfo => ++$count };
}
%hash = (allowed => \#allowed, solution => ++$count);
push (#ants, \%hash);
}
for ($i = 0; $i < scalar(#ants); ++$i) {
%test = %{$ants[$i]};
print "=>", $test{solution}, "\n";
#temp = #{$test{allowed}};
for ($j = 0; $j < scalar(#temp); ++$j) {
print $j, ":", $temp[$j]->{stem}, " ", $temp[$j]->{hinfo}, "\n";
}
}
Output:
=>21
0:16 16
1:18 18
2:20 20
=>21
0:16 16
1:18 18
2:20 20
2) When I want push a hash to an array I use {} instead of () because if I don't it gave me error. How does it see when I use {}?
I can answer this Question 2.
When you use () perl sees as list of elements. When you use {} perl sees it as reference to a hash.
So When you do this: push #Arr, (x=>2, y=>5); four elements will be pushed to #Arr: x, 2, y, 5. Which is not your intention.
But When you do this: push #Arr , {x => 2, y => 5}; a single reference to an anonymous hash containing x and y as the keys, and 2 and 5 as the respective values will be pushed to #Arr.

Generating a unordered list hash from a array

I am trying to use hashes to generate an unordered list that i can further use in a jstree. But this array has to be generated only from an array that has been passed thru .
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
I want the output to look something like
$data = {
"New Order" => {
"Recurring Order" =>{
Previously cancelled Order = 1
}
}
};
I can simply do
my $data{$array[0]}{$array[1]}{$array[2]} = 1
but the array can be of n variables, so it becomes a bit more complicated than that. I am thinking of recursion, but i have been sitting here for the last hour trying to figure that out
This will generate the data structure as you have defined it. Not sure why you'd want it though.
my #input = ( "New Order","Recurring Order","Previously Cancelled Order");
my $data = 1;
$data = {$_ => $data} for reverse #input;
use Data::Dump;
dd $data;
If you're just wanting to randomize your array, then use List::Util;
use List::Util qw(shuffle);
my #newOrder = shuffle #input;
sub recursive {
my $v = shift #_;
return #_>1 ? { $v => recursive(#_) } : { $v => #_ };
}
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
use Data::Dumper; print Dumper recursive(#array, 1);
output
$VAR1 = {
'New Order' => {
'Recurring Order' => {
'Previously Cancelled Order' => 1
}
}
};

Iterate through a hash and an array in Perl

I have an array and a hash:
#arraycodons = "AATG", "AAAA", "TTGC"... etc.
%hashdictionary = ("AATG" => "A", "AAAA" => "B"... etc.)
I need to translate each element of the array for the corresponding value in hashdictionary. However, I obtain a wrong translation.....
To see the problem, I have printed $codon (each element of the array), but each codon is repeated several times..... and It shouldn't.
sub translation() {
foreach $codon (#arraycodons) {
foreach $k (keys %hashdictionary) {
if ($codon == $k) {
$v = $hashdictionary{$k};
print $codon;
}
}
}
}
I don't know if I've explained my problem well enough, but I can't go on with my code if this doesn't work...
Many thanks in advance.
You appear to be looping through the keys of your hash (also known as a "dictionary") to find your desired key. This defeats the purpose of a hash (also known as a "dictionary") - the primary advantage of which is ultra fast lookups of a key.
Try, instead of
foreach $codon (#arraycodons) {
foreach $k (keys %hashdictionary) {
if ($codon == $k) {
$v = $hashdictionary{$k};
print $codon;
}
}
}
this:
foreach $codon (#arraycodons) {
my $value = $hashdictionary{$codon};
print( "$codon => $value\n" );
}
or:
foreach my $key ( keys %hashdictionary ) {
my $value = $hashdictionary{$key};
print( "$key => $value\n" );
}
my #mappedcodons = map {$hashdictionary{$_}}
grep (defined $hashdictionary{$_},#arraycodons);
or
my #mappedcodons = grep ($_ ne "", map{$hashdictionary{$_} || ""} #arraycodons);
my #words = ("car", "house", "world");
my %dictionary = ("car" => "el coche", "house" => "la casa", "world" => "el mundo");
my #keys = keys %dictionary;
foreach(#words) {
my $word = $_;
foreach(#keys) {
if($_ eq $word) { # eq, not ==
my $translation = $dictionary{$_};
print "The Spanish translation of $word is $translation\n";
}
}
}

Resources