Perl arrays and arrayrefs - arrays

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";

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 data structures - looping an array of hashes inside a hash

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";
}

perl, removing elements from array in for loop

will the following code always work in perl ?
for loop iterating over #array {
# do something
if ($condition) {
remove current element from #array
}
}
Because I know in Java this results in some Exceptions, The above code is working for me for now, but I want to be sure that it will work for all cases in perl. Thanks
Well, it's said in the doc:
If any part of LIST is an array, foreach will get very confused if you
add or remove elements within the loop body, for example with splice.
So don't do that.
It's a bit better with each:
If you add or delete a hash's elements while iterating over it,
entries may be skipped or duplicated--so don't do that. Exception: In
the current implementation, it is always safe to delete the item most
recently returned by each(), so the following code works properly:
while (($key, $value) = each %hash) {
print $key, "\n";
delete $hash{$key}; # This is safe
}
But I suppose the best option here would be just using grep:
#some_array = grep {
# do something with $_
some_condition($_);
} #some_array;

Perl: Hash within Array within Hash

I am trying to build a Hash that has an array as one value; this array will then contain hashes. Unfortunately, I have coded it wrong and it is being interpreted as a psuedo-hash. Please help!
my $xcHash = {};
my $xcLine;
#populate hash header
$xcHash->{XC_HASH_LINES} = ();
#for each line of data
$xcLine = {};
#populate line hash
push(#{$xcHash->{XC_HASH_LINES}}, $xcLine);
foreach $xcLine ($xcHash->{XC_HASH_LINES})
#psuedo-hash error occurs when I try to use $xcLine->{...}
$xcHash->{XC_HASH_LINES} is an arrayref and not an array. So
$xcHash->{XC_HASH_LINES} = ();
should be:
$xcHash->{XC_HASH_LINES} = [];
foreach takes a list. It can be a list containing a single scalar (foreach ($foo)), but that's not what you want here.
foreach $xcLine ($xcHash->{XC_HASH_LINES})
should be:
foreach my $xcLine (#{$xcHash->{XC_HASH_LINES}})
foreach $xcLine ($xcHash->{XC_HASH_LINES})
should be
foreach $xcLine ( #{ $xcHash->{XC_HASH_LINES} } )
See http://perlmonks.org/?node=References+quick+reference for easy to remember rules for how to dereference complex data structures.
Golden Rule #1
use strict;
use warnings;
It might seem like a fight at the beginning, but they will instill good Perl practices and help identify many syntactical errors that might otherwise go unnoticed.
Also, Perl has a neat feature called autovivification. It means that $xcHash and $xcLine need not be pre-defined or constructed as references to arrays or hashes.
The issue faced here is to do with the not uncommon notion that a scalar can hold an array or hash; it doesn't. What it holds is a reference. This means that the $xcHash->{XC_HASH_LINES} is an arrayref, not an array, which is why it needs to be dereferenced as an array using the #{...} notation.
Here's what I would do:
my %xcHash;
for each line of data:
push #{$xcHash{XC_HASH_LINES}},$xcLine;

Resources