Perl: Passing by reference does not modify the hash - perl

My understanding was that in Perl we pass hashes to functions by reference
Consider the following example, where we modify the hash in the modifyHash function
#!/usr/local/bin/perl
my %hash;
$hash{"A"} = "1";
$hash{"B"} = "2";
print (keys %hash);
print "\n";
modifyHash(\%hash);
print (keys %hash);
print "\n";
sub modifyHash {
my $hashRef = #_[0];
my %myHash = %$hashRef;
$myHash{"C"} = "3";
print (keys %myHash);
print "\n";
}
The output of this script is:
AB
ABC
AB
I would have expected it to be:
AB
ABC
ABC
...as we pass the hash by reference.
What concept am I missing here about passing hashes to functions?

That's because when you do my %myHash = %$hashRef;, you're taking a copy of the dereferenced $hashref and putting it into %myHash which is the same thing as my %myHash = %hash;, so you're not working on the referenced hash at all.
To work on the hash specified by the reference, try this...
sub modifyHash {
my $hashRef = $_[0];
$hashRef->{"C"} = "3";
print (keys %$hashRef);
print "\n";
}
As pointed out by ThisSuitIsBlackNot in the comments below, #_[0] is better written as $_[0]. You should always be using use strict; and use warnings;, as this would have been caught. Because you're sending in a reference, you could also have used my $hashRef = shift;.

The problem is with the assignment:
my %myHash = %$hashRef;
This is akin to saying:
$x = 5;
$y = $x;
You're not setting $y to reference the same spot in memory, you're just giving the value of $x to $y. In your example, you're creating a new hash (%myHash) and giving it the value of the hash stored at $hashRef. Any future changes are to the new hash, not the original.
If you want to manipulate the original, you should do something like:
${$hashRef}{"C"} = "3";
or
$hashRef->{"D"} = 4;
There might be a more elegant way of doing it, but as far as I know you want to work with the hash reference.

Related

Passing a Perl Hash by reference plus scalar variable

I'm trying to pass from Main.pl to a sub (in ReadConfigFile.pm) a hash by reference and a scalar value. The scalar variable is the path to a config file and once this file is opened I want to fill a hash with some of its values. How do i pass a hash by reference and a scalar so that I then have the hash values available to use in Main.pl
I have done a lot of reading but cant get this to work. I realise i cant do = #_; in my sub as that is creating a new hash.
Ive tried following the prototype method, this fills the hash ok but back in Main.pl the hash is empty.
Main.pl
# Read the config file. Return 3 scalars and a hash
my %apps;
my ($schema, $directory, $staticFile) = readConfigFile(\%apps, $configFilePath);
my %app_list = %apps; # ive tried this in, out and in a variety of states
foreach my $name (sort keys %app_list) {
print "\nMAIN $name";
}
# this is empty
ReadConfigFile.pm
sub readConfigFile (\%$) {
my ($apps_ref, $configFilePath) = #_;
# also tried
# $apps_ref = shift but then configFilePath is empty
# linearray is each line from open config file split by :
$apps_ref{$lineArray[1]}{id} = $lineArray[1];
$apps_ref{$lineArray[1]}{name} = $lineArray[2];
$schema = $lineArray[1];
$directory = $lineArray[1];
$staticFile = $lineArray[1];
return ($schema, $directory, $staticFile);
configFile.txt
APP:1101:ACTIVITY
APP:1102:EVENTS
APP:1103:PERFORMANCE
APP:1104:LOCATION
STATIC_FILE:static_file.sql
SCHEMA:CAASS
DIRECTORY:CAASS
I want to get the 3 scalar variable returned and the hash so i can use them throughout Main.pl and pass to other subs.
I have also tried passing in just the configfilename and returning 4 variables, the 3 scalars and the hash.
I expect someone will crack this in minutes but i just cant work out the combination of \ and # and % and $ to make it work.
Thanks for any help or ideas.
Edit 1:
Main.pl
my %apps;
my ($schema, $directory, $staticFile) = readConfigFile(\%apps, $configFilePath);
foreach my $name (sort keys %apps) {
print "\nMAIN $name";
}
ReadConfigFile
sub readConfigFile () {
my $apps_ref = shift;
my $configFilePath = $_[0];
#Fill It
$apps_ref{$lineArray[1]}{id} = $lineArray[1];
$apps_ref{$lineArray[1]}{name} = $lineArray[2];
# This shows results
foreach my $name (sort keys %apps_ref) {
print "\nreadConfigFile $name";
}
But the values arent coming back into Main.pl
edit 2:
So im still interested in how the above can be made to work. But ive attacked it a different way and it works
Main.pl
my ($schema, $directory, $staticFile, %apps) = readConfigFile($configFilePath);
foreach my $name (sort keys %apps) {
print "\nMAIN $name";
}
ReadConfigFile
sub readConfigFile () {
my $configFilePath = $_[0];
my %apps;
#Fill It
%apps{$lineArray[1]}{id} = $lineArray[1];
$apps{$lineArray[1]}{name} = $lineArray[2];
foreach my $name (sort keys %apps) {
print "\nreadConfigFile $name";
}
return ($schema, $directory, $staticFile, %apps);
Both sets off output show.
There is no implicit 'pass by reference' in Perl*. Everything is passed the same way - as a list of scalars, by alias (thus passing a hash itself will instead pass the list of its keys and values*). But you can create a reference, pass it, and then dereference it to use it - and references can be copied around without copying the underlying structure.
use strict;
use warnings;
my %hash;
my $ref = \%hash;
my $copy = $ref;
$copy->{a} = 1;
print "$ref->{a}\n"; # also 1
References will maintain their referenced structure after a my (...) = #_; or my $foo = shift; assignment in a subroutine.
use strict;
use warnings;
sub foo {
my ($ref, $key) = #_;
$ref->{$key} = 42;
}
my %hash;
foo(\%hash, 'foo');
print "$hash{foo}\n"; # 42
See https://p3rl.org/REF for the relevant documentation on Perl references.
Since you are already passing a reference, there is no need for your (\%$) prototype: you can just remove it from the subroutine definition.
*except sort of with prototypes, but it's better to avoid them in most cases.

perl passing hash reference not behaving as expected

(NOTE: I sort of figured this out, see all the way at the end)
It's kind of late and I've been staring at this code for far too long. I finally wrote a short test program to test hashes and passing them by reference and it is not behaving as I expect. I'm sure there is something very simple I'm missing ... can anyone spot it?
#!/usr/bin/perl
use Data::Dumper;
my %hash = ();
print "BEFORE ADDING KEYS\n";
print Dumper (\%hash);
test (\%hash, 10);
print "AFTER ADDING KEYS\n";
print Dumper (\%hash);
sub test {
my %hash = %{$_[0]};
my $number = $_[1];
if ($number == 0) { return; }
print "BEFORE ADDING KEY HASH_REF=$_[0] NUMBER=$number\n";
print Dumper (\%hash);
$hash{$number} = $number;
print "AFTER ADDING KEY\n";
print Dumper (\%hash);
test ($_[0], $number - 1);
}
I expect this code to add the numbers 10 to 1 into my hash, but instead the hash gets wiped out and doesn't contain anything once the test routine finishes recursing. What am I missing? Here is the output:
BEFORE ADDING KEYS
$VAR1 = {};
BEFORE ADDING KEY HASH_REF=HASH(0xdb82fd0) NUMBER=10
$VAR1 = {};
AFTER ADDING KEY
$VAR1 = {
'10' => 10
};
BEFORE ADDING KEY HASH_REF=HASH(0xdb82fd0) NUMBER=9
$VAR1 = {};
AFTER ADDING KEY
$VAR1 = {
'9' => 9
};
...
BEFORE ADDING KEY HASH_REF=HASH(0xdb82fd0) NUMBER=1
$VAR1 = {};
AFTER ADDING KEY
$VAR1 = {
'1' => 1
};
AFTER ADDING KEYS
$VAR1 = {};
Changing this line:
$hash{$number} = $number;
to:
$_[0]->{$number} = $number;
made everything work as expected. Why does the first statement modify a fresh local %hash when I would expect this local %hash to be pointing to the same de-referenced hash reference I originally passed into the routine?
Everything works as intended. You first statement in the test sub makes a copy of passed hash:
my %hash = %{$_[0]};
To mutate passed hash, you should work with hashref, like:
my $hashref= $_[0];
$hashref->{key} = 'val';
This approach will change original hash, not it's copy.

How do I return an array and a hashref?

I want to make a subroutine that adds elements (keys with values) to a previously-defined hash. This subroutine is called in a loop, so the hash grows. I don’t want the returning hash to overwrite existing elements.
At the end, I would like to output the whole accumulated hash.
Right now it doesn’t print anything. The final hash looks empty, but that shouldn’t be.
I’ve tried it with hash references, but it does not really work. In a short form, my code looks as follows:
sub main{
my %hash;
%hash=("hello"=>1); # entry for testing
my $counter=0;
while($counter>5){
my(#var, $hash)=analyse($one, $two, \%hash);
print ref($hash);
# try to dereference the returning hash reference,
# but the error msg says: its not an reference ...
# in my file this is line 82
%hash=%{$hash};
$counter++;
}
# here trying to print the final hash
print "hash:", map { "$_ => $hash{$_}\n" } keys %hash;
}
sub analyse{
my $one=shift;
my $two=shift;
my %hash=%{shift #_};
my #array; # gets filled some where here and will be returned later
# adding elements to %hash here as in
$hash{"j"} = 2; #used for testing if it works
# test here whether the key already exists or
# otherwise add it to the hash
return (#array, \%hash);
}
but this doesn’t work at all: the subroutine analyse receives the hash, but its returned hash reference is empty or I don’t know. At the end nothing got printed.
First it said, it's not a reference, now it says:
Can't use an undefined value as a HASH reference
at C:/Users/workspace/Perl_projekt/Extractor.pm line 82.
Where is my mistake?
I am thankful for any advice.
Arrays get flattened in perl, so your hashref gets slurped into #var.
Try something like this:
my ($array_ref, $hash_ref) = analyze(...)
sub analyze {
...
return (\#array, \#hash);
}
If you pass the hash by reference (as you're doing), you needn't return it as a subroutine return value. Your manipulation of the hash in the subroutine will stick.
my %h = ( test0 => 0 );
foreach my $i ( 1..5 ) {
do_something($i, \%h);
}
print "$k = $v\n" while ( my ($k,$v) = each %h );
sub do_something {
my $num = shift;
my $hash = shift;
$hash->{"test${num}"} = $num; # note the use of the -> deference operator
}
Your use of the #array inside the subroutine will need a separate question :-)

Perl modifying hash reference in subroutine

I am having trouble understanding the hash references and changing the hash in place, instead of returning it. I want to write a sub routine which will return a value from hash and also modify the hash. I was facing some issues while coding for it. So, I wrote the following basic code to understand modifying the hash in place.
#!/usr/local/bin/perl
#Check hash and array references
#Author: Sidartha Karna
use warnings;
use strict;
use Data::Dumper;
sub checkHashRef{
my ($hashRef, $arrVal) = #_;
my %hashDeref = %{$hashRef};
$hashDeref{'check'} = 2;
push(#{$arrVal}, 3);
print "There:" ;
print Dumper $hashRef;
print Dumper %hashDeref;
print Dumper $arrVal
}
my %hashVal = ('check', 1);
my #arrVal = (1, 2);
checkHashRef(\%hashVal, \#arrVal);
print "here\n";
print Dumper %hashVal;
print Dumper #arrVal;
The output observed is:
There:$VAR1 = {
'check' => 1
};
$VAR1 = 'check';
$VAR2 = 2;
$VAR1 = [
1,
2,
3
];
here
$VAR1 = 'check';
$VAR2 = 1;
$VAR1 = 1;
$VAR2 = 2;
$VAR3 = 3;
From the output, I inferred that, changes to hashDeref are not modifying the data in the reference. Is my understanding correct? Is there a way to modify the hash variable in place instead of returning it.
This is making a (shallow) copy of %hashVal:
my %hashDeref = %{$hashRef};
The hash-ref $hashRef still points to %hashVal but %hashDeref doesn't, it is just a copy. If you want to modify the passed hash-ref in-place, then work with the passed hash-ref:
sub checkHashRef{
my ($hashRef, $arrVal) = #_;
$hashRef->{'check'} = 2;
#...
That will leave your changes in %hashVal. In the array case, you never make a copy, you just dereference it in-place:
push(#{$arrVal}, 3);
and the change to $arrVal shows up in #arrVal.

About the usage of hash reference in Perl

This reports syntax error:
$hash={a=>2};
print %{$hash}{a};
But this works:
print each(%{$hash})
Why??
To get an element from a hashref, you take the normal code for getting a hash element: $foo{'bar'}, and replace the name of the hash, not including the sigil, with the hashref: $$hash{'bar'}. Your % would only be used to dereference to the full hash, as in your each case, not just an element.
More helpful hints at http://perlmonks.org/?node=References+quick+reference.
Maybe this will help you understand why it's wrong...
$hash = {a => 2}; #Works: $hash is a reference to the hash
%foo = %{$hash}; #Now, we've dereferenced the hash to %foo
# Wherever we have "$hash", we can now use "foo"...
print %foo{a}; #Whoops! Doesn't work.
print %hash{a}; #And, neither did this!
print $foo{a}; #No problem! Use '$" when talking about a single hash element
print ${$hash}{a} #Same as above.
print each %foo; #Each takes a hash (with "%" sign)
print each %{$hash}; #Same as above.
print $hash->{a} #Syntactic Sugar: Same as ${$hash{a}} or $$hash{a}
Yeah, just like print %hash{a} doesn't work even though each(%hash) does.
each(%hash) ==> each(%{ $ref })
print($hash{a}) ==> print(${ $ref }{a})
You were missing the lookup '->'.
print %{$hash}{a};
should be:
print %{$hash}->{a};
You declare it as $ but then try to cast to a hash and retrieve the value, not sure why.
Just retrieve like so:
print $hash->{a};
My personal preference when it comes to hashes:
$hash1->{a} = 1;
print $hash1->{a}, "\n"; # prints '1'
Multi level:
$hash2->{a}{a} = 1;
$hash2->{a}{b} = 2;
print $hash2->{a}{a}, "\n"; # prints '1'
print $hash2->{a}{b}, "\n"; # prints '2'
Looping:
while (my ($key, $value) = each %{$hash1})
{
print $key, "\n"; # prints 'a'
print $value, "\n"; # prints '1'
}