Passing a Perl Hash by reference plus scalar variable - perl

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.

Related

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.

How to pass a hash table by refrence Perl

I am currently trying to implement a suffix tree using Perl, however, when I attempt to set the reference for the tree function, the reference location is not set, if I pass the address via a string then check the text in the string vs the location of the hash table and they are different. Any help is appreciated!
use strict;
use warnings;
use Data::Dumper;
my $count = 0;
my $str; # holds the complete string
my %root;
# takes in all lines of code
open(IN, '<:encoding(UTF-8)', $ARGV[0]) or die "Could not open file '$ARGV[0]' $!\n";
while (<IN>) {
chomp;
# concatinates with string
$str .= $_;
}
# closes input
close(IN);
#length of input string
my $l_size = length($str) - 1;
#recursively makes
sub tree {
#recursive root
my %treeRoot;
#checking incomming data
print "1 ".Dumper(\#_)."\n";
#checking incomming data
print "2 ".Dumper(\%root)."\n";
#attempts to set tree's refrence
\%treeRoot, $count = #_;
#checking incomming data
print "3 ".Dumper(\%root)."\n";
#checking incomming data
print "4 ".$count."\n";
#leaf for each node
my %leaf;
for (my $i = 0; $i < $l_size; $i++) {
#creates alphabet tree
$treeRoot { substr($str, $i, 1) } = %leaf;
}
#checking incomming data
print "5 ".Dumper(\%root)."\n";
while ($count > 0) {
#checking incomming data
print "loop 6 ".Dumper(\%root)."\n";
$count--;
#checking incomming data
print "loop 7 ".$count."\n";
#recursion not implamented yet
#tree(\$treeRoot{'a'}, $count);
}
}
tree(\%root, 2);
#print Dumper(\%root);
You need parentheses to disambiguate. This:
\%treeRoot, $count = #_;
means this:
\%treeRoot;
$count = #_;
Because the assignment operator = has higher precedence than the comma operator ,. The warning that you got from running that code tells you this: Useless use of reference constructor in void context.
To pass the arguments correctly, you need parentheses:
(\%treeRoot, $count) = #_;
Unfortunately, this does not work, because you cannot assign to a reference this way. The following error tells you that: Can't modify reference constructor in list assignment.
So what you need is to pass the reference to a scalar:
my ($href, $count) = #_;
print $href->{'value'};
I think this method is a bit backwards, though. Passing variables by reference is likely to become a source of bugs. A more natural solution is to use the return value of the subroutine to assign values:
sub foo {
my %hash;
$hash{'value'} = ....
....
return \%hash;
}
my $hashref = foo();
print $hashref->{'value'};
Your question isn't actually how to pass a hash reference, but how to receive it, as the following will not work:
\%treeRoot, $count = #_;
Basically, you need to assign your reference to a scalar like so:
use strict;
use warnings;
sub example_sub {
my ($hashref, $count) = #_;
# Add two values to the hash:
$hashref->{newkey} = 'val';
$hashref->{newkey2} = 'val2';
}
my %root;
example_sub(\%root, 2);
use Data::Dump;
dd \%root;
Outputs:
{ newkey => "val", newkey2 => "val2" }
If you don't want to modify your original hash, you can assign the values to a new hash within the sub:
my %newhash = %$hashref;
For more info on working with references, check out: perlref - Perl references and nested data structures

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 :-)

What is wrong with my hash of hash code?

Can anyone tell me where I am wrong? I can't figure it out....
Basically what my code is trying to do is to read the files and create a hash for each file, these hashes are organized into on hash. The user would input two parameters, one is the key of the outer hash, and the other is for the one inside.
The ones I input are city and PIT; the same as the parameter I wrote before the line that breaks down....
I tried thousands of times, I keep getting this error: Can't use an undefined value as a HASH reference I have commented that line out in the code.
The two files are cities.txt; school.txt.
Their content are just as below:
PIT\tPittsburgh
NY\tNewYork
#!/bin/perl -w
use strict;
use Data::Dumper;
our %hash_all = ();
sub readHash{
my #vars = #_;
my $filename = $vars[0];
my %iptable = ();
if(open(IN,$filename.".txt")) {
while(<IN>) {
my #tmp = split(/\t/);
$iptable{$tmp[0]} = $tmp[1];
}
}
return %iptable;
}
sub loadAll{
my %school = readHash("school");
my %city = readHash("cities");
$hash_all{school} = \%school;
$hash_all{city} = \%city;
print Dumper(\%hash_all);
}
sub queryValue{
my #pars = #_;
my $key1 = $pars[0];
my $key2 = $pars[1];
print "key1".$key1;
print "key2".$key2;
print Dumper(\%hash_all);
my %temp = %{$hash_all{"city"}};#THIS LINE WORKS
print $temp{"PIT"}; #THIS LINE WORKS
my %temp2 = %{$hash_all{$key1}};#THIS LINE HAS AN ERROR
print $temp2{$key2};
}
loadAll();
my $par1 = <>;
my $par2 = <>;
queryValue($par1,$par2);
Your problem is probably that when you read in $par1 and $par2, they include newlines at the end. So you end up looking for a hash key like "city\n", which is not the same as "city".
Make sure you use chomp on your input parameters, like chomp($par1) . That should take care of it.

"Variable $foo will not stay shared" Warning/Error in Perl While Calling Subroutine

Update3: If you like this posting please don't upvote me but upvote the genius answer by DVK below.
I have the following subroutines:
use warnings;
#Input
my #pairs = (
"fred bill",
"hello bye",
"hello fred",
"foo bar",
"fred foo");
#calling the subroutine
my #ccomp = connected_component(#pairs);
use Data::Dumper;
print Dumper \#ccomp;
sub connected_component {
my #arr = #_;
my %links;
foreach my $arrm ( #arr ) {
my ($x,$y) = split(/\s+/,$arrm);;
$links{$x}{$y} = $links{$y}{$x} = 1;
}
my %marked; # nodes we have already visited
my #stack;
my #all_ccomp;
for my $node (sort keys %links) {
next if exists $marked{$node};
#stack = ();
connected($node);
print "#stack\n";
push #all_ccomp, [#stack];
}
sub connected {
no warnings 'recursion';
my $node = shift;
return if exists $marked{$node}; # Line 43
$marked{$node} = 1;
push #stack, $node; # Line 45
my $children = $links{$node}; # Line 46
connected($_) for keys %$children;
}
return #all_ccomp;
}
But why it gives this message:
Variable "%marked" will not stay shared at mycode.pl line 43.
Variable "#stack" will not stay shared at mycode.pl line 45.
Variable "%links" will not stay shared at mycode.pl line 46.
Is it harmful? Error? How can fix my code so that it get rid of that message?
Update1: I update the code that runs as is with the actuall error message
Update2: I tried to modify using sub as DVK suggested. And it WORKED!
use warnings;
#Input
my #pairs = (
"fred bill",
"hello bye",
"hello fred",
"foo bar",
"fred foo");
#calling the subroutine
my #ccomp = connected_component(#pairs);
use Data::Dumper;
print Dumper \#ccomp;
sub connected_component {
my #arr = #_;
my %links;
foreach my $arrm ( #arr ) {
my ($x,$y) = split(/\s+/,$arrm);;
$links{$x}{$y} = $links{$y}{$x} = 1;
}
my %marked; # nodes we have already visited
my #stack;
my #all_ccomp;
my $connected_sub;
$connected_sub = sub {
no warnings 'recursion';
my $node = shift;
return if exists $marked{$node};
$marked{$node} = 1;
push #stack, $node;
my $children = $links{$node};
&$connected_sub($_) for keys %$children;
};
for my $node (sort keys %links) { # Line 43
next if exists $marked{$node};
#stack = ();
&$connected_sub($node);
#print "#stack\n";
push #all_ccomp, [#stack]; # Line 49
}
return #all_ccomp;
}
As per perldoc's perldiag for that error, your problem is that the inner sub is referencing a lexical variable (%marked) defined in the outer sub.
The fix is in the third paragraph (use anonymous sub):
(Warning; closure) An inner (nested) named
subroutine is referencing a lexical
variable defined in an outer named
subroutine.
When the inner subroutine is called,
it will see the value of the outer
subroutine's variable as it was before
and during the first call to the
outer subroutine; in this case, after
the first call to the outer subroutine
is complete, the inner and outer
subroutines will no longer share a
common value for the variable. In
other words, the variable will no
longer be shared.
This problem can usually be solved by
making the inner subroutine anonymous,
using the sub {} syntax. When inner
anonymous subs that reference
variables in outer subroutines are
created, they are automatically
rebound to the current values of such
variables.
Fixed code using anonymous sub:
# ....
my $connected_sub;
$connected_sub = sub {
no warnings 'recursion';
my $node = shift;
return if exists $marked{$node}; # Line 280
$marked{$node} = 1;
push #stack, $node; # Line 282
my $children = $links{$node}; # Line 283
&$connected_sub($_) for keys %$children;
};
for my $node (sort keys %links) {
next if exists $marked{$node};
#stack = ();
&$connected_sub($node);
#print "#stack\n";
push #all_ccomp, [#stack];
}
# ....
Another (maybe simpler) way out is declare variables as "our" instead of "my"
So,
our %marked;
instead of
my %marked;
etc.
When getting a diagnostic message from perl, it's usually a good idea to check out perldiag to find out what it means. That manpage also happens to cover the warning you're getting.
Basically, named subroutines don't nest in the way you were expecting them to. Solutions include using anonymous inner subroutines, not nesting named subroutines and just passing state on between them explicitly, or using something like mysubs from CPAN.
This error can also happen if you've accidentally re-declared shared variables in the main thread of a script,
`
use vars qw(%types %colors);
my %types = (...); # bad
%colors = (...); # good
`