Perl modifying hash reference in subroutine - perl

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.

Related

Perl/DBI selectrow_array scope confusion

So, I've got a list of files and I'm checking a database table to see if an entry exists and pull the id and destination filename. If not there, insert an entry and repull the entry (note the id is auto increment so no matter I have to do a second query.)
The issue is when I re-pull the query after the insert, the variable it's going into is lexical (i think that's the correct wording) and once I leave the scope of the if (!defined) block, it loses its value.
#lookup file db entry
my ($fileId, $destFilename) = $dbh->selectrow_array("select fileId, destFilename from myTable where sourceFilename = '$file'");
if (! defined $fileId) {
# calculate out what the destination filename should be here..
# add missing entry into table
($fileId, $destFilename) = $dbh->selectrow_array("select fileId, destFilename from myTable where sourceFilename = '$file'");
print Dumper $destFilename;
}
print Dumper $destFilename;
This will result in:
$VAR1 = "correctfilenamehere"
$VAR1 = undef
I have tried defining the variables before assigning them via the selectrow_array call. I've tried changing above from my to our for these variables. I'm confused on why it's doing this.
Also to note, this code is within another block so those variables are already lexical to that scope. I had presumed they would be available in the child blocks, but it's not really working that way as far as I can see.
The code you posted does not exhibit the behaviour you describe.
$ perl -e'
use strict;
use warnings;
use Data::Dumper qw( Dumper );
sub f { $_[0] ? (4, "abc") : () }
my ($fileId, $destFilename) = f(0);
if (!defined $fileId) {
($fileId, $destFilename) = f(1);
print Dumper $destFilename;
}
print Dumper $destFilename;
'
$VAR1 = 'abc';
$VAR1 = 'abc';
You could get the described bahaviour if you introduced a new variable with the same name.
$ perl -e'
use strict;
use warnings;
use Data::Dumper qw( Dumper );
sub f { $_[0] ? (4, "abc") : () }
my ($fileId, $destFilename) = f(0);
if (!defined $fileId) {
my ($fileId, $destFilename) = f(1);
print Dumper $destFilename;
}
print Dumper $destFilename;
'
$VAR1 = 'abc';
$VAR1 = undef;
Found the issue, a bit of code was making a lexical variable of the same name and I wasn't noticing, so the parent $destFilename was being replaced with a local variant.

Why is my hash getting flattened?

I am passing an array of references by reference to a subroutine. When I try to deference it in a sub-routine, it gives a flattened hash. How can I fix this? I don't want to have a flat hash and I am unable determine the reason for this.
I am sure that I am making a mistake somewhere but not able to spot it. Any comments/suggestions are totally welcome! Looking forward to hear from this wonderful community! Thanks in advance.
updated problem statement:
Basically I am looking to pass a hash by reference to a sub-routine. And my issue is that when I accept it in the subroutine with a scalar variable, and then I try to de-reference it with % symbol, I still get a flat hash.
update: There was a confusion.As I was checking whether my hash is flat or not - I checked only with print Dumper %hash when I should actually have actually checked with print Dumper \%hash. Lack of this piece of information caused this issue.
Script:
#!/usr/bin/perl
use strict ;
use warnings ;
use Data::Dumper ;
my %h = (moe => "joe", toe => "poe") ;
my #a = (1,2,3,4) ;
my #refs = \(%h,#a) ;
sub sample()
{
my $ref = shift ;
my #refs = #{$ref} ;
print "What I got in the sub! Seems OK!!\n" ;
print Dumper #refs, "\n" ;
my %h = %{$refs[0]} ;
my #a = #{$refs[1]} ;
print "I am not able to dereference this :(. Please help!! Hash is flat :(\n" ;
print Dumper %h ;
print Dumper #a ;
}
&sample(\#refs) ;
OUTPUT:
23:34:17[Host#User]$ ./test.pl
What I got in the sub! Seems OK!!
$VAR1 = {
'moe' => 'joe',
'toe' => 'poe'
};
$VAR2 = [
1,
2,
3,
4
];
$VAR3 = '
';
I am not able to dereference this :(. Please help!! Hash is flat :(
$VAR1 = 'moe';
$VAR2 = 'joe';
$VAR3 = 'toe';
$VAR4 = 'poe';
$VAR1 = 1;
$VAR2 = 2;
$VAR3 = 3;
$VAR4 = 4;
There's nothing to fix. You have what you wanted. You have a hash in %h and an array in #a.
But Data::Dumper takes a list of arguments and it treats each of its arguments as a separate variable to dump. So when you pass either a hash or an array to Dumper(), they will be unrolled into a list and you'll get them displayed as separate variables.
If you want to see the structure of an array or a hash using Dumper(), you should pass in a reference to the data structure instead.
print Dumper \%h;
print Dumper \#a;
Of course, that's effectively what you're doing on your first call to Dumper().
print Dumper #refs;
I should also point out that you have a couple of errors in your code that (fortunately?) cancel each other out. You define your subroutine sample with an empty prototype (sample() { ... }) which means that you will get a fatal error if you pass it any arguments. But when you call the subroutine, you use an & (&sample(#refs)) and one of the effects of that is to turn off prototype checking - so it works even though you pass arguments to the subroutine.
Best to omit the prototype completely (sub sample { ... }) and call the subroutine without the ampersand (sample(#refs))).

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.

Perl: Passing by reference does not modify the hash

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.

Perl: Hash values change after returning from a subroutine

I'm working with hashes in perl, but don't understand why the hash value changes in the following:
use strict;
sub test
{
my ($value_ref) = #_;
my %value = %$value_ref;
$value{'abc'}{'xyz'} = 1;
}
my %hash;
$hash{'abc'}{'xyz'} = 0;
test (\%hash);
print "$hash{'abc'}{'xyz'}\n";
The above returns 1, why doesn't it return 0 like this it does here?
use strict;
sub test
{
my ($value_ref) = #_;
my %value = %$value_ref;
$value{'abc'} = 1;
}
my %hash;
$hash{'abc'} = 0;
test (\%hash);
print "$hash{'abc'}\n";
I imagine it has to do with how I'm passing in %hash. What am I missing?
You are getting incorrect results because when you make your copy of the hash using my %value = %$value_ref; you are only getting the top-level keys. The second level, with the key 'xyz', is the one where value is actually stored so any changes to that reference are carried over. You are performing a shallow copy when you need a deep copy.
Luckily there is a CPAN module for that!
use strict;
use Storable qw(dclone);
sub test
{
my ($value_ref) = #_;
my %value = %{ dclone($value_ref) };
$value{'abc'}{'xyz'} = 1;
}
my %hash;
$hash{'abc'}{'xyz'} = 0;
test (\%hash);
print "$hash{'abc'}{'xyz'}\n"; # prints 0
This is because the "second level" of your hash (IOW: the value for key 'abc') is itself a hash reference. While you're copying the contents of the hash referenced by $value_ref in your assignment:
my %value = %$value_ref;
...this is only doing a "shallow" copy.
When you modify the value for key 'xyz', you're modifying the same hash that is in the original structure passed to the sub.