Passing an array and a variable to a function in Perl - perl

I have an issue passing an array and a variable to a function. For example, I have the following.
my #the_array = ("hello", "hey");
CallFunction(#the_array, "random")
sub CallFunction{
my (#array_ref, $ran_variable) = #_;
foreach $element (#array_ref){
print $element ."\n";
}
}
I would want the following output
hello
hey
But I get the other variable in the output, and I don't know why.
hello
hey
random

The following assignment will put all the values in parameter list #_ into #array_ref:
my (#array_ref, $ran_variable) = #_;
You have two options.
Reorder the passing of parameters, so that the array is at the end:
my #the_array = ( "hello", "hey" );
CallFunction( "random", #the_array );
sub CallFunction {
my ( $ran_variable, #array ) = #_;
for my $element (#array) {
print $element . "\n";
}
}
Or pass the array by reference:
my #the_array = ( "hello", "hey" );
CallFunction( \#the_array, "random" );
sub CallFunction {
my ( $arrayref, $ran_variable ) = #_;
for my $element (#$arrayref) {
print $element . "\n";
}
}
Minor Note — Naming a normal array #array_ref is a little confusing. Save the ref suffix for variables actually holding references.

Related

Array ref empty when received by a sub

I am trying to access elements of arrays by reference, passing references into a sub. Here is my code snippet:
my #arr1 = (1,2);
my #arr2 = (3,4);
my #arr3;
push #arr3, \#arr1;
push #arr3, \#arr2;
for my $i (#arr3) {
print "$i\n";
}
print "Entered Sub func()\n";
for my $i (#arr3) {
func($i);
}
sub func{
my $par = shift;
print $par."\n";
}
print "------------------------\n";
for my $elem(#$par) {
print $elem."\n";
}
And here is the ouput:
C:\Users\ag194y>perl arrs.pl
ARRAY(0x357b28)
ARRAY(0x3575e8)
Entered Sub func()
ARRAY(0x357b28)
ARRAY(0x3575e8)
------------------------
C:\Users\ag194y>
I was expecting to access the elements of #arr1 and a#rr2 with the for loop in the sub, but it looks like array refs are empty. What am I doing wrong? Many thanks.
I think the problem is, loop being outside of func. You are calling func twice, and only after that you are looping through $par, which is undefined at the time.
You might be looking for something like:
sub func{
my $par = shift;
print $par."\n";
print "------------------------\n";
for my $elem (#$par){
print $elem."\n";
}
}

Storing an array in a hash

So, this doesn't work, and I have no idea why. I've tried every possible variation. But nothing works. I'm ready to take a chainsaw to my server, but hopefully you can prevent that:
sub getQuestMarkers {
#database stuff
...
my %package;
while(my ($key, $lat, $lng) = $sth->fetchrow_array()) {
$package{$key} = ($lat,$lng);
}
...
return %package;
}
my %markers = getQuestMarkers();
while(my( $key, $value) = each %markers) {
print "$key: #value - $value[0] $value[1]\n";
}
Use brackets [ ] to create an array reference, not parens ( );
As written, your code throws away the first value $lat. Write it like this instead:
$package{$key} = [$lat,$lng];
You can pull out the values like this:
my ($lat,$lng) = #{ $package{$key} };
In your code, you could print out the values by dereferencing them:
print "$key: " . $value->[0] . " " . $value->[1] . "\n";
Have a look at perldoc perlreftut.

Perl: How to turn array into nested hash keys

I need to convert a flat list of keys into a nested hash, as follow:
my $hash = {};
my #array = qw(key1 key2 lastKey Value);
ToNestedHash($hash, #array);
Would do this:
$hash{'key1'}{'key2'}{'lastKey'} = "Value";
sub to_nested_hash {
my $ref = \shift;
my $h = $$ref;
my $value = pop;
$ref = \$$ref->{ $_ } foreach #_;
$$ref = $value;
return $h;
}
Explanation:
Take the first value as a hashref
Take the last value as the value to be assigned
The rest are keys.
Then create a SCALAR reference to the base hash.
Repeatedly:
Dereference the pointer to get the hash (first time) or autovivify the pointer as a hash
Get the hash slot for the key
And assign the scalar reference to the hash slot.
( Next time around this will autovivify to the indicated hash ).
Finally, with the reference to the innermost slot, assign the value.
We know:
That the occupants of a hash or array can only be a scalar or reference.
That a reference is a scalar of sorts. (my $h = {}; my $a = [];).
So, \$h->{ $key } is a reference to a scalar slot on the heap, perhaps autovivified.
That a "level" of a nested hash can be autovivified to a hash reference if we address it as so.
It might be more explicit to do this:
foreach my $key ( #_ ) {
my $lvl = $$ref = {};
$ref = \$lvl->{ $key };
}
But owing to repeated use of these reference idioms, I wrote that line totally as it was and tested it before posting, without error.
As for alternatives, the following version is "easier" (to think up)
sub to_nested_hash {
$_[0] //= {};
my $h = shift;
my $value = pop;
eval '$h'.(join '', map "->{\$_[$i]}", 0..$#_).' = $value';
return $h;
}
But about 6-7 times slower.
I reckon this code is better - more amenable to moving into a class method, and optionally setting a value, depending on the supplied parameters. Otherwise the selected answer is neat.
#!/usr/bin/env perl
use strict;
use warnings;
use YAML;
my $hash = {};
my #array = qw(key1 key2 lastKey);
my $val = [qw/some arbitrary data/];
print Dump to_nested_hash($hash, \#array, $val);
print Dump to_nested_hash($hash, \#array);
sub to_nested_hash {
my ($hash, $array, $val) = #_;
my $ref = \$hash;
my #path = #$array;
print "ref: $ref\n";
my $h = $$ref;
$ref = \$$ref->{ $_ } foreach #path;
$$ref = $val if $val;
return $h;
}
Thxs for the good stuff!!!
I did it the recursive way:
sub Hash2Array
{
my $this = shift;
my $hash = shift;
my #array;
foreach my $k(sort keys %$hash)
{
my $v = $hash->{$k};
push #array,
ref $v eq "HASH" ? $this->Hash2Array($v, #_, $k) : [ #_, $k, $v ];
}
return #array;
}
It would be interesting to have a performance comparison between all of these solutions...
Made a better version of axeman's i think. Easier to understand without the -> and the \shift to me at least. 3 lines without a subroutine.
With subroutine
sub to_nested_hash {
my $h=shift;
my($ref,$value)=(\$h,pop);
$ref=\%{$$ref}{$_} foreach(#_);
$$ref=$value;
return $h;
}
my $z={};
to_nested_hash($z,1,2,3,'testing123');
Without subroutine
my $z={};
my $ref=\$z; #scalar reference of a variable which contains a hash reference
$ref=\%{$$ref}{$_} foreach(1,2,3); #keys
$$ref='testing123'; #value
#with %z hash variable just do double backslash to get the scalar reference
#my $ref=\\%z;
Result:
$VAR1 = {
'1' => {
'2' => {
'3' => 'testing123'
}
}
};

array to hash in perl

I have a source list from which I am picking up random items and populating the destination list. The item that are in the list have a particular format. For example:
item1{'name'}
item1{'date'}
etc and many more fields.
while inserting into the destination list I check for unique names on items and insert it into that list. For this I have to traverse the entire destination list to check if an item with a given name exists and if not insert it.
I thought it would be nice if I make the destination list as hash instead of a list again so that I can look up for the item faster and efficiently. I am new to Perl and am not getting how to do this. Anybody, Please help me on how to insert an item, find for a particular item name, and delete an item in hash?
How can I make both the name and date as key and the entire item as value?
my %hash;
Insert an item $V with a key $K?
$hash{$K} = $V
Find for a particular name / key $K?
if (exists $hash{$K}) {
print "it is in there with value '$hash{$K}'\n";
} else {
print "it is NOT in there\n"
}
Delete a particular name / key?
delete $hash{$K}
Make name and date as key and entire item as value?
Easy Way: Just string everything together
set: $hash{ "$name:$date" } = "$name:$date:$field1:$field2"
get: my ($name2,$date2,$field1,$field2) = split ':', $hash{ "$name:$date" }
del: delete $hash{ "$name:$date" }
Harder Way: Store as a hash in the hash (google "perl object")
set:
my %temp;
$temp{"name"} = $name;
$temp{"date"} = $date;
$temp{"field1"} = $field1;
$temp{"field2"} = $field2
$hash{"$name:$date"} = \$temp;
get:
my $find = exists $hash{"$name:$date"} ? $hash{"$name:$date"} : undef;
if (defined find) { # i.e. it was found
printf "field 1 is %s\n", $find->{"field1"}
} else {
print "Not found\n";
}
delete:
delete $hash{"$name:$date"}
It is not easy to understand what you are asking because you do not describe the input and the desired outputs specifically.
My best guess is something along the lines of:
#!/usr/bin/perl
use strict; use warnings;
my #list = (
q(item1{'name'}),
q(item1{'date'}),
);
my %lookup;
for my $entry ( #list ) {
my ($name, $attrib) = $entry =~ /([^{]+){'([^']+)'}/;
$lookup{ $name }{ $attrib } = $entry;
}
for my $entry ( keys %lookup ) {
my %entry = %{ $lookup{$entry} };
print "#entry{keys %entry}\n"
}
use YAML;
print Dump \%lookup;
Output:
item1{'date'} item1{'name'}
---
item1:
date: "item1{'date'}"
name: "item1{'name'}"
If you know what items, you are going to need and what order you'll need them in
for keys, then re parsing the key is of questionable value. I prefer to store
them in levels.
$hash{ $h->{name} }{ $h->{date} } = $h;
# ... OR ...
$hash{ $h->{date} }{ $h->{name} } = $h;
foreach my $name ( sort keys %hash ) {
my $name_hash = $hash{$name};
foreach my $date ( keys %$name_hash ) {
print "\$hash{$name}{$date} => " . Dumper( $name_hash->{$date} ) . "\n";
}
}
For arbitrary levels, you may want a traversal function
sub traverse_hash (&#) {
my ( $block, $hash_ref, $path ) = #_;
$path = [] unless $path;
my ( #res, #results );
my $want = wantarray;
my $want_something = defined $want;
foreach my $key ( %$hash_ref ) {
my $l_path = [ #$path, $key ];
my $value = $hash_ref->{$key};
if ( ref( $value ) eq 'HASH' ) {
#res = traverse_hash( $block, $value, $l_path );
push #results, #res if $want_something && #res;
}
elsif ( $want_something ) {
#res = $block->( $l_path, $value );
push #results, #res if #res;
}
else {
$block->( $path, $value );
}
}
return unless $want_something;
return $want ? #results : { #results };
}
So this does the same thing as above:
traverse_hash {
my ( $key_path, $value ) = #_;
print( '$hash{' . join( '}{', #$key_path ) . '} => ' . ref Dumper( $value ));
();
} \%hash
;
Perl Solution
#!/usr/bin/perl -w
use strict;
use Data::Dumper;
sub main{
my %hash;
my #keys = qw(firstname lastname age); # hash's keys
# fname lname age
# --------|--------|-----
my #arr = ( [ 'foo1', 'bar1', '1' ],
[ 'foo2', 'bar2', '2' ],
[ 'foo3', 'bar3', '3' ]
);
# test if array set up correctly
print "\$arr[1][1] : $arr[1][1] \n"; # bar2
# loads the multidimensional array into the hash
for my $row (0..$#arr){
for my $col ( 0..$#{$arr[$row]} ){
my $itemnum = "item" . ($row+1); # using the item# format you used
$hash{$itemnum}->{$keys[$col]} = $arr[$row][$col];
}
}
# manually add a 4th item
$hash{item4} = {"firstname", "foo", "lastname", "bar", "age", "35"};
# How to Retrieve
# -----------------------
# single item pull
print "item1->firstname : $hash{item1}->{firstname} \n"; # foo1
print "item3->age : $hash{item3}->{age} \n"; # 3
# whole line 1
{ local $, = " ";
print "full line :" , %{$hash{item2}} , "\n"; # firstname foo2 lastname bar2 age 2
}
# whole line 2
foreach my $key (sort keys %{$hash{item2}}){
print "$key : $hash{item2}{$key} \n";
}
# Clearer description
#print "Hash:\n", Dumper %hash;
}
main();
This should be used in addition to the accepted answer. Your question was a little vague on the array to hash requirement, perhaps this is the model you are looking for?

How can I use hashes as arguments to subroutines in Perl?

I have a function that is doing some calculations and then passes some properties into another subroutine like so:
sub get_result {
my $id = 1;
my %diet = ( result => 28,
verdict => 'EAT MORE FRUIT DUDE...'
);
my %iq = ( result => 193,
verdict => 'Professor Einstien'
);
print_result($id, %diet, %iq);
}
sub print_result {
my $id = shift;
my %d = #_;
my %i = #_;
print "IQ: $id\n";
print "DIET RESULT: $d{result}\n";
print "DIET VERDICT: $d{verdict}\n";
print "IQ RESULT: $i{result}\n";
print "IQ VERDICT: $i{verdict}\n";
}
My problem is that the results printed in (DIET RESULT, DIET VERDICT) and (IQ SCORE, IQ RESULT) are both the same. As if variable %d and %i are being populated with the same variables. Any ideas why this is?
If I try shifting all three variables like so:
my $id = shift;
my %d = shift;
my %i = shift;
I get the following error:
Odd number of elements in hash assignment
When you pass an array (or hash) to a subroutine, the subroutine will get a list of the values (or key values pairs). That's why you cannot pass two arrays (or two hashes), because the subroutine won't know where the first array ends and the second one starts.
To work around this problem, you should pass in references instead:
my %hash1 = ( foo => 'bar' );
my %hash2 = ( bar => 'baz' );
subroutine( \%hash1, \%hash2 );
sub subroutine {
my ( $hashref1, $hashref2 ) = #_;
print $hasref1->{foo}, $hashref2->{bar};
}
PS: Apart from the conceptual problem, your code also features this:
my %d = #_;
my %i = #_;
If %d and %i are both assigned the same value, it shouldn't come as a surprise when they are the same afterwards.
You might want to check out my book Intermediate Perl, about a third of which deals with references and how to work with them. This includes passing complex data structures into subroutines as well as other ways that references make your life easier. :)
some_sub( \%hash );
some_sub( { key => 'value' } );
some_sub( $hash_ref );
sub some_sub {
my( $hash_ref ) = #_;
...
}
When you pass in %diet and %iq, they both get flattened into the arg array, so in your print_result, %d contains all items in %diet and %iq.
To solve, use references of the %diet and %iq:
print_result($id, \%diet, \%iq);
Then in print_result:
my $id = shift;
my %d = %{+shift};
my %i = %{+shift};
use strict;
use warnings;
sub get_result {
...
print_result( $id, \%diet, \%iq );
# or
print_result( $id, {%diet}, {%iq} );
}
sub print_result{
my( $id, $diet_h, $iq_h ) = #_;
my %diet = %$diet_h;
my %iq = %$iq_h;
...
}
Or:
use strict;
use warnings;
sub print_result($\%\%);
sub get_result{
...
print_result($id, %diet, %iq);
}
sub print_result($\%\%){
my( $id, $diet_h, $iq_h ) = #_;
my %diet = %$diet_h;
my %iq = %$iq_h;
...
}