Perl data structures - looping an array of hashes inside a hash - arrays

I need a data structure to keep metadata about a field in a database, which I'm going to access to write dynamic SQL.
I'm using a hash to store things like the name, maybe data type, etc. And most importantly, an array of hashes containing information about the values I want to query out of the field, and the name I want to alias them with.
When I try to access elements of that array, I get:
Global symbol "%elem" requires explicit package name at test.pl line 18.
It sounds like maybe it's having trouble registering the fact that the loop variable representing the array elements is a hash, not a scalar. If I try:
foreach my %elem
then I get:
Missing $ on loop variable at test.pl line 17 (#1)
So far I can't find the relevant Perl documentation that addresses this.
#!/usr/local/bin/perl
use warnings;
use strict;
use diagnostics;
use POSIX 'strftime';
my %struct = (
#"field" = "foobar",
"values" => [
{value => "Y", name => "FOO"}
, {value => "N", name => "BAR"}
]
);
foreach my $elem (#{$struct->{'values'}}) {
print $elem->{'value'};
}
I expect the program to print "YN" to the console.
UPDATE, as someone pointed out I needed to use %hash->{'ref'} in the loop addressing. I added it. Now, I get a notification saying that using a hash as a reference is deprecated (?) but it is printing to the console now!

When I tried running your code, I got a different error than you reported:
Global symbol "$struct" requires explicit package name
This is because you've defined a hash %struct, not a hashref $struct, so you don't need to dereference it. Thus, I changed the line
foreach my $elem (#{$struct->{'values'}}) {
to
foreach my $elem (#{$struct{'values'}}) {
(note no -> to dereference) and it ran perfectly, no errors or warnings, and emitted the output
YN
as expected.

%struct is a hash, not a hash reference. Therefore, $struct->{'values'} is not the correct way to access the values key.
for my $elem (#{$struct{values}}) {
print "$elem->{value}\n";
}

Related

Can't use string as an ARRAY ref while strict refs in use

Getting an error when I attempt to dump out part of a multi dimensional hash array. Perl spits out
Can't use string ("somedata") as an ARRAY ref while "strict refs" in use at ./myscript.pl
I have tried multiple ways to access part of the array I want to see but I always get an error. I've used Dumper to see the entire array and it looks fine.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper qw(Dumper);
use String::Util qw(trim);
my %arrHosts;
open(my $filehdl, "<textfile.txt") || die "Cannot open or find file textfile.txt: $!\n";
while( my $strInputline = <$filehdl> ) {
chomp($strInputline);
my ($strHostname,$strOS,$strVer,$strEnv) = split(/:/, $strInputline);
$strOS = lc($strOS);
$strVer = trim($strVer);
$strEnv = trim($strEnv);
$strOS = trim($strOS);
$arrHosts{$strOS}{$strVer}{$strEnv} = $strHostname;
}
# If you want to see the entire database, remove the # in front of Dumper
print Dumper \%arrHosts;
foreach my $machine (#{$arrHosts{solaris}{10}{DEV}}) {
print "$machine\n";
}
close($filehdl);
The data is in the form
machine:OS:OS version:Environment
For example
bigserver:solaris:11:PROD
smallerserver:solaris:11:DEV
I want to print out only the servers that are solaris, version 11, in DEV. Using hashes seems the easiest way to store the data but alas, Perl barfs when attempting to print out only a portion of it. Dumper works great but I don't want to see everything. Where did I go wrong??
You have the following:
$arrHosts{$strOS}{$strVer}{$strEnv} = $strHostname;
That means the following contains a string:
$arrHosts{solaris}{10}{DEV}
You are treating it as if it contains a reference to an array. To group the hosts by OS+ver+env, replace
$arrHosts{$strOS}{$strVer}{$strEnv} = $strHostname;
with
push #{ $arrHosts{$strOS}{$strVer}{$strEnv} }, $strHostname;
Iterating over #{ $arrHosts{solaris}{10}{DEV} } will then make sense.
My previous code also had the obvious problem whereby if the combo of OS, Version, and Environment were the same it wrote over previous data. Blunderful. Push is the trick

Perl array referencing reversing

I am newbie to perl. And have been working with CSV files, JSON strings, arrays and hashes.
I have written this code, but it is giving me error. I want to use $header_copy in foreach loop.
1. my #headers=qw/January February March April May June/;
2. my $header_copy=\#headers;
3. print("$header_copy->[2]"); # Prints "March" Correctly
4. print("$header_copy[2]"); #Gives Error.
Error:
Global symbol "#header_copy" requires explicit package name at line 4
And I want to use $header_copy in for loop: like:
foreach $i ($header_copy){ **code..** }
You are taking the reference of #headers array using \#headers into $header_copy. So, before accessing it, you need to dereference it.
There are two ways(actually more than that) for it:
Using Arrow operator(->) - Most suitable for accesing a single item from arrayref
Using # { } - Suitable for iterating over arrayrefs .
$header_copy[2] will give error because you are accessing an element from arrayref without dereferencing it. The interpreter assumes it as an array #header_copy not an arrayref because the syntax says it.
Below program summarizes both approach:
#!/usr/bin/perl
use strict;
use warnings;
# define # headers
my #headers = qw/January February March April May June/;
# taken refrence of #headers array into $header_copy
my $header_copy = \#headers;
# dereferencing using arrow(->) operator
print $header_copy -> [2],"\n";
# derefrencing for iteration using #{...}
foreach(#{ $header_copy }) {
print $_,"\n";
}
The error 'Global symbol "..." requires explicit package name' is Perl-speak for "you're trying to use an undeclared variable". It even tells you the name of the undeclared variable - in this case it's #header_copy.
And if you look in your code, you'll see there's no declaration for an array called #header_copy. Oh, you have a scalar variable called $header_copy. And that contains a reference to an array (the array #headers). But that has no connection to an array called #header_copy.
So why does Perl think you want to use the array #header_copy? Well in the last line of your code, you use $header_copy[2] - which means "the third element in array #header_copy". And that generates an error because (as I've already pointed out) you don't have that array.
On the previous line you use $header_copy->[2]. And that works fine because ->[...] is the correct way to look up an element in an array that you have a reference to.
The important thing to realise is that $header_copy->[2] and $header_copy[2] mean two completely different things and refer to two completely different variables.
You also ask how you can get back to an array that you have a reference to (this is called "dereferencing"). That is simple. In general you use:
#{ expression that returns an array reference }
So, in your case, that would be:
#{ $header_copy }
But in cases where your expression is a scalar variable, you can simplify it by omitting the braces, so it becomes:
#$header_copy
So you want:
foreach my $i (#$header_copy) {
...
}
# reads data
my #headers=qw/January February March April May June/;
# use a foreach loop and store local copy of item nr in $headeritem_copy
foreach my $headeritem_copy (#headers) {
print("$headeritem_copy\n");
}
my #headers=qw/January February March April May June/;
my $header_copy=\#headers;
print("$header_copy->[2]"); # Prints "March" Correctly
De-reference the particular index
print("#{$header_copy}->[2]"); #Gives Error.

Perl arrays and arrayrefs

I am migrating environments and have code that is duplicated across both environments. When I call Dumper on the same part of the code in both environments I get two different outputs.
First environment
'key' => 'ARRAY(0x7f867b846850)', # Array contains element 'expected element'
Second environment
'key' => [ 'expected element' ],
I've seen other posts that suggests these are both ArrayRefs and can be regarded and treated as the same, is that the case? I haven't put the details of how the array is created as it's not what I'm investigating at the minute, I just want to know if these are identical, and why Dumper might treat them different.
Thanks
Actually, that implies that your array reference is being turned into a string before being placed in the hash. A hash element is always an array reference, not an array. Dumper automatically traverses references.
In your first example it looks like the text you get when you convert a reference to a string. I guess just quoting the ref or concatenation would do it.
Something like this should illustrate the point I think?
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my $array_ref = [ 1, 2, 3 ];
print Dumper $array_ref;
print $array_ref -> [0],"\n";
#because we concat here, perl converts it to a string
$array_ref .= "";
print Dumper $array_ref;
#so this errors
print $array_ref -> [0],"\n";

Looping through array contained in a hash

So I have an object that contains an array:
package MyObject;
sub new {
my($type) = #_;
my $self->{Params}{Status}{Packages} = [];
}
I have a add new package sub which appends onto this "Package" array like:
sub add_package {
my($self, $package_obj) = #_;
push $self->{Params}{Status}{Packages}, $package;
}
Now when I go to find all the packages in my array I have issues. Whenever I try and pull out the packages like this:
foreach my $package($self->{Params}{Status}{Packages}) {
# do something with $package.
}
This only loops through one time. Now from what I understand the hash actually stores a pointer to the array so I tried to do:
foreach my $package(#$self->{Params}{Status}{Packages}) {
# do something with $package.
}
But then there is an error saying that $self is not an array. I did notice when I do:
scalar $self->{Params}{Status}{Packages};
It returns:
#ARRAY(0xSome Address);
What am I missing? And how can I use a foreach loop to go through my array?
$self->{Params}{Status}{Packages} is a reference to an array, in Perl terminology. When you have a reference to something, put the right character in front of it to dereference it. If the reference is more than just a name with possibly some sigils in front, you need to surround it with braces. It's a matter of precedence: #$self->{Params}{Status}{Packages} is parsed as (#$self)->{Params}{Status}{Packages}, but you need
#{$self->{Params}{Status}{Packages}}
i.e. the array referenced by the expression $self->{Params}{Status}{Packages}.
In this case, you need to wrap it all in the array dereference block #{} so perl knows which portion you're trying to dereference...
for my $package (#{ $self->{Params}{Status}{Packages} }){
print "$package\n";
}
Also, just to keep things consistent, I prefer to always deref the array with the block when extracting, or inserting:
push #{ $self->{Params}{Status}{Packages} }, $package;
UPDATE: As of 5.24.0+, autoderef (using keys(), values() or each() with a reference) will almost certainly be removed, and replaced with postfix references. However, using the #{} and %{} will continue to be supported, and is backwards compatible, so I'd recommend using them at all times.
In my view, the clearest way to do this is to extract the array reference to a temporary scalar variable, which makes accessing the array very straightforward
my $packages = $self->{Params}{Status}{Packages};
for my $package ( #$packages ) {
# do something with $package.
}
Also, if you have use strict and use warnings enabled as you should, your add_package subroutine will produce the message
push on reference is experimental
This isn't something you can safely ignore. Experimental features may change their behaviour or disappear completely in later versions of Perl, and it is unwise to make use of them in production code. You can fix your subroutine in a similar way, like this
sub add_package {
my ($self, $package_obj) = #_;
my $packages = $self->{Params}{Status}{Packages};
push #$packages, $package;
}

Perl -- DBI selectall_arrayref when querying getting Not Hash Reference

I am very new to perl (but from a c# background) and I am trying to move some scripts to a windows box.
Due to some modules not working easily with windows I have changed the way it connects to the DB.
I have an sqlserver DB and I had a loop reading each row in a table, and then within this loop another query was sent to select different info.
I was the error where two statements can't be executed at once within the same connection.
As my connection object is global I couldn't see an easy way round this, so decided to store the first set of data in an array using:
my $query = shift;
my $aryref = $dbh->selectall_arrayref($query) || die "Could not select to array\n";
return($aryref);
(this is in a module file that is called)
I then do a foreach loop (where #$s_study is the $aryref returned above)
foreach my $r_study ( #$s_study ) {
~~~
my $surveyId=$r_study->{surveyid}; <-------error this line
~~~~
};
When I run this I get an error "Not a hash reference". I don't understand?!
Can anyone help!
Bex
You need to provide the { Slice => {} } parameter to selectall_arrayref if you want each row to be stored as a hash:
my $aryref = $dbh->selectall_arrayref($query, { Slice => {} });
By default, it returns a reference to an array containing a reference to an array for each row of data fetched.
$r_study->{surveyid} is a hashref
$r_study->[0] is an arrayref
this is your error.
You should use the second one
If you have a problem with a method, then a good first step is to read the documentation for that method. Here's a link to the documentation for selectall_arrayref. It says:
This utility method combines
"prepare", "execute" and
"fetchall_arrayref" into a single
call. It returns a reference to an
array containing a reference to an
array (or hash, see below) for each
row of data fetched.
So the default behaviour is to return a reference to an array which contains an array reference for each row. That explains your error. You're getting an array reference and you're trying to treat it as a hash reference. I'm not sure that the error could be much clearer.
There is, however, that interesting bit where it says "or hash, see below". Reading on, we find:
You may often want to fetch an array
of rows where each row is stored as a
hash. That can be done simple using:
my $emps = $dbh->selectall_arrayref(
"SELECT ename FROM emp ORDER BY ename",
{ Slice => {} }
);
foreach my $emp ( #$emps ) {
print "Employee: $emp->{ename}\n";
}
So you have two options. Either switch your code to use an array ref rather than a hash ref. Or add the "{ Slice => {} }" option to the call, which will return a hash ref.
The documentation is clear. It's well worth reading it.
When you encounter something like "Not a hash reference" or "Not an array reference" or similar you can always take Data::Dumper to just dump out your variable and you will quickly see what data you are dealing with: arrays of arrayrefs, hashes of something etc.
And concerning reading the data, this { Slice => {} } is most valuable addition.

Resources