CGI/Perl Guide | Learning Center | Forums | Advertise | Login
Site Search: in

  Main Index MAIN
INDEX
Search Posts SEARCH
POSTS
Who's Online WHO'S
ONLINE
Log in LOG
IN

Home: Perl Programming Help: Beginner:
Passing a hash into a function

 



sfo_sc
Novice

Jan 31, 2005, 9:33 AM

Post #1 of 22 (2373 views)
Passing a hash into a function Can't Post

If I am passing two variable to a function:

function($file, %hash)

When I use the arguments in the function,
$file should be $_[0]
but how can I use the %hash argument if I want to do something like $hash{$something}++ ?
It seems like $_[1]{$something}++ is wrong.
What is the right way to use the %hash it is pass into the function as an argument?


KevinR
Veteran


Jan 31, 2005, 4:14 PM

Post #2 of 22 (2369 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post

If you are passing different bits of data to a function, and you want the data to remain seperate, you use references and dereferencing. If you try and pass multiple lists (hashes or arrays) to a function or sub routine the lists are flattened, they become one long list. Here is an example of passing a scalar and a hash to a sub routine:


Code
&function(\$file, \%hash); 

sub function {
my $file = $_[0];
my $refhash = $_[1];
print "$$file\n";
foreach my $keys (keys %{$refhash}){
print "$keys = $refhash->{$keys}\n";}
}


here is a link to a references tutorial:

http://search.cpan.org/dist/perl/pod/perlref.pod
-------------------------------------------------


sfo_sc
Novice

Jan 31, 2005, 4:23 PM

Post #3 of 22 (2368 views)
Re: [KevinR] Passing a hash into a function [In reply to] Can't Post

Thanks for the info.
If I want to pass the hash to a function and make some changes to the original hash table, is that possible? I do not want to use globle variable, so I want to pass the hash table into the function. But if I make changes of the has inside the function, it will not change the original hash right? How do I solve that problem?


KevinR
Veteran


Jan 31, 2005, 4:35 PM

Post #4 of 22 (2367 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
Thanks for the info.
If I want to pass the hash to a function and make some changes to the original hash table, is that possible? I do not want to use globle variable, so I want to pass the hash table into the function. But if I make changes of the has inside the function, it will not change the original hash right? How do I solve that problem?


Any changes you make to the hash in the function will change the original hash. The reference to the hash points to the address of the original hash. The reference is not passing a copy of the hash.

As far as I know its the same even if you are not using reference. Any data passed in the system array is the actual data, not copies of the data.
-------------------------------------------------


(This post was edited by KevinR on Jan 31, 2005, 4:36 PM)


davorg
Thaumaturge / Moderator

Feb 2, 2005, 2:06 AM

Post #5 of 22 (2360 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post

You need:

Code
function($file, $hash);

Then in the subroutine

Code
sub function { 
my ($file, %hash) = @_;

...
}


You can then use %hash within the function just like any ordinary hash (which is, after all, what it is!)

--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


davorg
Thaumaturge / Moderator

Feb 2, 2005, 2:18 AM

Post #6 of 22 (2359 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
If I want to pass the hash to a function and make some changes to the original hash table, is that possible? I do not want to use globle variable, so I want to pass the hash table into the function. But if I make changes of the has inside the function, it will not change the original hash right? How do I solve that problem?


When using something like


Code
function(%hash); 

sub function {
my %hash = @_;

...
}


Then the version of %hash within the function only contains a copy of the values in your original hash. And changes you make will be lost when you leave the function.

You have two options, depending on how complex your requirements are.

1/ You can return the updated hash from the function. This is by far the easiest solution.


Code
%hash = function(%hash); 

sub function {
my %hash = @_;

# do stuff

return %hash;
}


2/ You can pass a reference to the hash into the subroutine.


Code
function(\%hash); 

sub function {
my $hash_ref = shift;

my %hash = %$hash_ref;

# now, any changes you make to %hash will be reflected
# outside of the function
}


--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


(This post was edited by davorg on Feb 3, 2005, 10:06 AM)


sfo_sc
Novice

Feb 2, 2005, 9:14 AM

Post #7 of 22 (2356 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

Cool thanks man. This explains really well. Smile


KevinR
Veteran


Feb 2, 2005, 12:35 PM

Post #8 of 22 (2353 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

Dave, it would seem I am wrong, or partially wrong in what I said :


Quote
Any changes you make to the hash in the function will change the original hash. The reference to the hash points to the address of the original hash. The reference is not passing a copy of the hash.

As far as I know its the same even if you are not using reference. Any data passed in the system array is the actual data, not copies of the data.


Is there a difference between using a reference and not using a reference when passing data to a sub as far as affecting the original list, be it array or hash or even scalars?
-------------------------------------------------


davorg
Thaumaturge / Moderator

Feb 3, 2005, 2:20 AM

Post #9 of 22 (2346 views)
Re: [KevinR] Passing a hash into a function [In reply to] Can't Post

Kevin,

I'll reply in a bit more detail later on (when I get some more time), but in the meantime here's a bit of code to play with which might explain a bit about what is going on.


Code
#!/usr/bin/perl 

use strict;
use warnings;

my %hash = (1 => 100, 2 => 200, 3 => 300);

double(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

double2(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

sub double {
for (@_) {
$_ = $_ * 2;
}
}

sub double2 {
my %h = @_;

$h{$_} *= 2 for keys %h;

@_ = %h;
}


--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


sfo_sc
Novice

Feb 3, 2005, 9:58 AM

Post #10 of 22 (2343 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post


In Reply To

Code
function(\%hash); 

sub function {
my %hash_ref = shift;

my %hash = %$hash_ref;

# now, any changes you make to %hash will be reflected
# outside of the function
}


Hey Dav, how come you need to do %hash_ref = shift? What does that do? Instead of doing that, can't you do %hash = shift?


davorg
Thaumaturge / Moderator

Feb 3, 2005, 10:07 AM

Post #11 of 22 (2340 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post

Sorry, there was an error in my post (which I've now corrected).

I meant


Code
my $hash_ref = shift;


($hash_ref, not %hash_ref)

--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


sfo_sc
Novice

Feb 3, 2005, 10:29 AM

Post #12 of 22 (2339 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

What does the $hash_ref = shift do? Is that what you have to do every time to use the reference of a hash?


sfo_sc
Novice

Feb 3, 2005, 10:31 AM

Post #13 of 22 (2338 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post


In Reply To
Kevin,

I'll reply in a bit more detail later on (when I get some more time), but in the meantime here's a bit of code to play with which might explain a bit about what is going on.


Code
#!/usr/bin/perl 

use strict;
use warnings;

my %hash = (1 => 100, 2 => 200, 3 => 300);

double(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

double2(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

sub double {
for (@_) {
$_ = $_ * 2;
}
}

sub double2 {
my %h = @_;

$h{$_} *= 2 for keys %h;

@_ = %h;
}



I run the code you provided above. Seems like both double and double 2 are doing the same thing? Hope to hear your more detail explaination later when you have time. Thanks.


KevinR
Veteran


Feb 3, 2005, 12:12 PM

Post #14 of 22 (2336 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

Thanks Dave.

from "Perl Cookbook" (first edition):


Quote
The scalars in @_ are implicit aliases for the ones passed in, not copies. That means changing the elements of @_ in a subroutine changes the values in the subroutine's caller. This is a holdover from before Perl had proper references.

So, we can write functions that leave their arguments intact, by copying the arguments to private variables like this:

@nums = (1.4, 3.5, 6.7);
@ints = int_all(@nums); # @nums unchanged
sub int_all {
my @retlist = @_; # make safe copy for return
for my $n (@retlist) { $n = int($n) }
return @retlist;
}

We can also write functions that change their caller's variables:

@nums = (1.4, 3.5, 6.7);
trunc_em(@nums); # @nums now (1,3,6)
sub trunc_em {
for (@_) { $_ = int($_) } # truncate each argument
}


I realize my copy of Perl Cookbook is a bit outdated, but it seems that is still the situation with perl, as your examples are indicating.
-------------------------------------------------


davorg
Thaumaturge / Moderator

Feb 3, 2005, 12:43 PM

Post #15 of 22 (2335 views)
Re: [KevinR] Passing a hash into a function [In reply to] Can't Post


In Reply To
Thanks Dave.

from "Perl Cookbook" (first edition):


Quote
The scalars in @_ are implicit aliases for the ones passed in, not copies. That means changing the elements of @_ in a subroutine changes the values in the subroutine's caller. This is a holdover from before Perl had proper references.

So, we can write functions that leave their arguments intact, by copying the arguments to private variables like this:

@nums = (1.4, 3.5, 6.7);
@ints = int_all(@nums); # @nums unchanged
sub int_all {
my @retlist = @_; # make safe copy for return
for my $n (@retlist) { $n = int($n) }
return @retlist;
}

We can also write functions that change their caller's variables:

@nums = (1.4, 3.5, 6.7);
trunc_em(@nums); # @nums now (1,3,6)
sub trunc_em {
for (@_) { $_ = int($_) } # truncate each argument
}


That's absolutely right. And nothing has changed there since the first edition of the cookbook.

However. Whilst this is easy to deal with when your argument is a list of scalars or an array, it's not as easy if it's a hash. Look at this...


Code
my %hash = (1 => 100, 2 => 200, 3 => 300);


Now here we've initialised a hash from a list. That's, of course, the standard way to create a hash in Perl.

But what happens when we flatten that hash back into a list in order to print it out.


Code
print join ' ', %hash;


On my computer, that prints out:

1 100 3 300 2 200

On you computer it could well print something different as you can't guarantee the order that a hash is stored in.

When you pass that hash into your subroutine, the hash is flattened into a list which is then stored in the array @_.

So there you are in your subroutine with a "flattened" hash in @_. You know that alternate elements of @_ contain the keys an values from the hash, but there is no way to know what order they are in. Which makes it really hard to alter specific values in the hash.

My first demo function (double) demostrated this approach. As you can't know which order the hash is in, you can't alter specific values, but you _can_ alter all of the values by iterating across the whole of the array. I have to admit that I was slightly surprised that only the values were updated and not the keys. I need to investigate why that works like that.

And then I had an idea. We can't know what order the keys are in when stored in @_, but that doesn't matter if we copy the data into another hash. But, of course, when we copy the data into a hash and change that hash, then we're no longer updating @_ and we're back to the situation where we loose the changes once we leave the subroutine. But (I found myself thinking) what happens if we copy the altered hash back into @_ at the end of the subroutine.

So I tried it (see "double2"). And it seems to work. But I can't ever recall seeing anyone else recommend this method. So I suspect there may be a good reason for not using it. I'll investigate further, but in the meantime I can't reall recommend you using it.

You should probably just stick with either returning the altered hash from the subroutine or passing a reference in as I discussed earlier.

I'm not sure I've been particularly clear here. I'm still trying to work this stuff out in my own head.

--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


sfo_sc
Novice

Feb 3, 2005, 1:40 PM

Post #16 of 22 (2331 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

So if we are using the passing reference way to do it, do we really have to use $hash_ref = shift?


sfo_sc
Novice

Feb 3, 2005, 2:22 PM

Post #17 of 22 (2327 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post


Code
#!/usr/bin/perl  

use strict;
use warnings;

my %hash = (1 => 100, 2 => 200, 3 => 300);

double(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

double2(%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

triple(\%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

sub double {
for (@_) {
$_ = $_ * 2;
}
}

sub double2 {
my %h = @_;

$h{$_} *= 2 for keys %h;

@_ = %h;
}

sub triple {
my $hash_ref = shift;
my %h = %$hash_ref;
#print %h;
my @v = values %h;
#print @v;
foreach (@v) {
$_ = $_ * 3;
}
#print @v;
}


The triple function is working within the sub routine but not affecting the original %hash. What did I do wrong here?


KevinR
Veteran


Feb 3, 2005, 2:29 PM

Post #18 of 22 (2327 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
So if we are using the passing reference way to do it, do we really have to use $hash_ref = shift?


You should be using strict, in which case the answer is yes. If you are not using strict then all your variables are global and you can access them without passing data in the system array to functions and there is no need to use a reference.
-------------------------------------------------


davorg
Thaumaturge / Moderator

Feb 4, 2005, 2:30 AM

Post #19 of 22 (2321 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
So if we are using the passing reference way to do it, do we really have to use $hash_ref = shift?


Well, $hash_ref = shift isn't really about whether you're using references or not, it's just a way to get the subroutine argument into a local variable.

It's a shortcut for


Code
my $var = shift @_;


which takes the first element for @_ and puts the value into $var. At the same time it removes the value from @_.

Another alternative would be to use


Code
my ($var) = @_;


This also takes the first value from @_ and puts it in $var. The difference is that this code _doesn't_ remove the value from @_.

--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks


sfo_sc
Novice

Feb 4, 2005, 2:51 PM

Post #20 of 22 (2313 views)
Re: [davorg] Passing a hash into a function [In reply to] Can't Post

Dav, could you please look at the edited version of your code when I try to use passing reference to triple each value in the hash and it didn't work after the subroutine ended.


KevinR
Veteran


Feb 4, 2005, 4:14 PM

Post #21 of 22 (2308 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
Dav, could you please look at the edited version of your code when I try to use passing reference to triple each value in the hash and it didn't work after the subroutine ended.


I'm not Dave but....

Its because you are no longer working with the original hash in your sub routine:


Code
sub triple {  
my $hash_ref = shift; <-- ref to original hash
my %h = %$hash_ref; <-- you made a copy of the hash
#print %h;
my @v = values %h; <-- another copy into an array of the values
#print @v;
foreach (@v) { <-- you are working with the copy, not the origianl
$_ = $_ * 3;
}
#print @v;
}


to change the original hash:


Code
my %hash = (1 => 100, 2 => 200, 3 => 300);   

triple(\%hash);

print "$_ => $hash{$_}\n" for sort keys %hash;

sub triple {
my $hash_ref = shift;
$hash_ref->{$_} *= 3 for keys %{$hash_ref};
}

-------------------------------------------------


davorg
Thaumaturge / Moderator

Feb 7, 2005, 2:12 AM

Post #22 of 22 (2276 views)
Re: [sfo_sc] Passing a hash into a function [In reply to] Can't Post


In Reply To
Dav, could you please look at the edited version of your code when I try to use passing reference to triple each value in the hash and it didn't work after the subroutine ended.


Like Kevin said, it's because you're updating the values in @v, which contains a _copy_ of the values from your hash, _not_ the values themselves.

When you return from your subroutine, @v vanishes taking with it all your changes.

If you want to do stuff like this then you _must_ work on @_ itself and not data copied from @_.

--
Dave Cross, Perl Hacker, Trainer and Writer
http://www.dave.org.uk/
Get more help at Perl Monks

 
 


Search for (options) Powered by Gossamer Forum v.1.2.0

Web Applications & Managed Hosting Powered by Gossamer Threads
Visit our Mailing List Archives