Accessing array of hashes in Perl - arrays

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.

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 )

Perl: Rearrange array based on element value

What would be the most efficient way to rearrange the array elements so that they are ordered & place with the corresponding index value (minus one)? The $dat variable being the max number of elements (which is never exceeded in the array but may or may not be present).
$dat = 14;
my #array = (1, 12, 14, 7, 8, 4)
In other words:
my #new_array = (1, undef, undef, 4, undef, undef, 7, 8, undef, undef, undef, 12, undef, 14);
***EDIT****
Fuller code snippet:
foreach $auth (keys %activity) {
my #value = #{ $activity{$auth} };
#value = uniq #value;
#value = sort #value;
s/^0// for #value;
my $count = scalar(grep $_, #value);
my $dat = max( #value );
#{$activity{$auth}} = #value;
}
Simple and fast (duplicates may occur in the original):
my #new_array;
$new_array[$_ - 1] = $_ for #array;
In-place (duplicates not allowed in original):
for (my $i = #array; $i--; ) {
my $j = $array[$i]-1;
if ($i < $j) {
$array[$j] = $j+1;
$array[$i] = undef;
}
elsif ($i > $j) {
#array[$i, $j] = #array[$j, $i];
redo;
}
}
In-place (duplicates may occur in the original):
for (my $i = #array; $i--; ) {
my $j = $array[$i]-1;
if ($i < $j) {
$array[$j] = $j+1;
$array[$i] = undef;
}
elsif ($i > $j) {
if ($array[$j] == $j+1) {
$array[$i] = undef;
}
else {
#array[$i, $j] = #array[$j, $i];
redo;
}
}
}
pop(#array) while #array && !defined($array[-1]);
You can create an new array with all values defined after finding the maximum value in the first, then in this new array undefine any value which is not in the first:
use strict;
use warnings;
use Data::Dumper;
my #array = ('1', '4', '3');
my $max = (sort { $b <=> $a } #array)[0]; #should be 4
print Dumper(\#array);
my #new_arr;
foreach my $index (0 .. ($max - 1)) {
$new_arr[$index] = ($index + 1);
#Array should be fully populated, #new_arr = ('1', '2', '3', '4');
$new_arr[$index] = 'undef' unless (grep {$_ eq $new_arr[$index]} #array);
#Values not in the original array should be set to undef
}
# #new_arr should be (''1', 'undef', '3', '4')
print Dumper(\#new_arr);
Output:
$VAR1 = [
'1',
'4',
'3'
];
$VAR1 = [
1,
'undef',
3,
4
];

Loop through multidimensional Hash of Arrays in Perl

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

PERL dynamically match arrays based on unique key

I'm trying to compare 2 huge arrays and want to use map. I am using unique key concept here to match the indexes.
My Arrays:
my #array1 = ( ['a','b','c','d'], ['e','f','g','h'], ['i','j','k','l'], ['m','n','o','p'], ['q','r','s','t']);
my #array2 = ( ['r','q','s','t'], ['b','a','c','d'], ['n','m','o','p'], ['f','e','g','h'], ['j','i','k','l']);
My unique Keys:
my #uk1 = (0,2,3);
my #uk2 = (1,2,3);
These arrays will be huge in size, over 30,000 indexes in each with over 20 elements in each index.
So effectively i create a map where
for ( my $j = 0; $j <= $#array1 ; $j++ )
{
my searchString;
for ( my $k = 0; $k <= $#uk1; $k++ )
{
if ( $k != 0 )
{
$searchString .= ","
}
$my searchString .= $array1[$j][$uk[$k];
}
my #result = map { $_ }
grep { join (",",$array2[$_][1],$array2[$_][2],$array2[$_][3]) ) =~ join(",",$array1[$j][0],$array1[$j][1],$array1[$j][2]) }
0 .. $#array;
}
returns matched indexes.
My problem is, how do i make this dependant on the unique keys? as the length of the unique key will keep changing and as far as i know i cannot dynamically create the $array2[$_] join part.
Hope my question is clear enough.
I want to have the logic that compares
$array1[$uk1[0]],$array1[$uk1[1]],$array1[$uk1[2]] and so on (depending on the number of keys in UK) with
$array2[$uk2[0]],$array2[$uk2[1]],$array2[$uk2[2]].......
Perhaps,
my #array1 = ( ['a','b','c','d'], ['e','f','g','h'], ['i','j','k','l'], ['m','n','o','p'], ['q','r','s','t']);
my #array2 = ( ['r','q','s','t'], ['b','a','c','d'], ['n','m','o','p'], ['f','e','g','h'], ['j','i','k','l']);
my #result;
for my $i (0 .. $#array1) {
push #result,
map { [$i, $_] }
grep {
"#{ $array1[$i] }[1,2,3]" eq "#{ $array2[$_] }[0,2,3]"
}
0 .. $#array2;
}
use Data::Dumper; print Dumper \#result;
output
$VAR1 = [
[
0,
1
],
[
1,
3
],
[
2,
4
],
[
3,
2
],
[
4,
0
]
];
What you want to use is an array slice:
But lets also make life easier:
for my $sample ( #array1 )
{
my $test= join(",", #$sample[#uk1]) ;
my #result = grep { $_ eq $test } map { join(",", #$_[#uk2] ) } #array2 ;
say "huzzah" if #result ;
}
Perl lets you specify multiple elements from an array via the "array slice":
my #list= ('a', 'b', 'c', 'd') ;
my #pieces= #list[1,3] ;

How to push an Array of Hashes to Array of Hashes?

I have this script
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %acc = ();
&insert_a(\%acc, 11);
&insert_p(\%acc, 111);
print Dumper %acc;
sub insert_a() {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
sub insert_p() {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
push $acc_ref->{"$_[0]"}{"c"}, #AoH;
}
where I am trying to insert AoH into c which also is an AoH, but I am getting
Type of arg 1 to push must be array (not hash element) at ./push.pl line 36, near "#AoH;"
Execution of ./push.pl aborted due to compilation errors.
Any ideas how to do that?
The specific problem is that you can only push to an array, so you first need to dereference the array, and then, since it's in a larger data structure, you want to set its value to a reference.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %acc = ();
# don't use & to call subs; that overrides prototypes (among other things)
# which you won't need to worry about, because you shouldn't be using
# prototypes here; they're used for something else in Perl.
insert_a(\%acc, 11);
insert_p(\%acc, 111);
# use \%acc to print as a nice-looking hashref, all in one variable
print Dumper \%acc;
# don't use () here - that's a prototype, and they're used for other things.
sub insert_a {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
# same here
sub insert_p {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
# You need to dereference the first array, and pass it a reference
# as the second argument.
push #{ $acc_ref->{"$_[0]"}{"c"} }, \#AoH;
}
I'm not quite sure that the resulting data structure is what you intended, but now that you have the program working and can see the resulting structure, you can modify it to get what you need.
Hash values are always scalar, so to store an array in a hash you need to store a reference to the array. Try using the following line, where the hash value is dereferenced to an array.
push #{ $acc_ref->{$_[0]}->{'c'} }, #AoH;
Do it like,
push #{$acc_ref->{"$_[0]"}->{"c"}}, #AoH;
or you can try $acc_ref->{"$_[0]"}->{"c"} = \#AoH;
Your script,
use strict;
use warnings
use Data::Dumper;
my %acc = ();
&insert_a(\%acc, 11);
&insert_p(\%acc, 111);
print Dumper %acc;
sub insert_a() {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
sub insert_p() {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
push #{$acc_ref->{"$_[0]"}->{"c"}}, #AoH;
}
Output:
$VAR1 = '11';
$VAR2 = {
'c' => [
{}
],
'a' => -1,
'b' => -1
};
$VAR3 = '111';
$VAR4 = {
'c' => [
{
'e' => -1,
'd' => -1
}
]
};

Resources