References in Perl: Array of Hashes - arrays

I want to iterate through a reference to an array of hashes without having to make local copies, but I keep getting Can't use string ("1") as an ARRAY ref while "strict refs" errors. Why? How do I fix it?
sub hasGoodCar {
my #garage = (
{
model => "BMW",
year => 1999
},
{
model => "Mercedes",
year => 2000
},
);
run testDriveCars( \#garage );
}
sub testDriveCars {
my $garage = #_;
foreach my $car ( #{$garage} ) { # <=========== Can't use string ("1") as an ARRAY ref while "strict refs" error
return 1 if $car->{model} eq "BMW";
}
return 0;
}

The line
my $garage = #_;
assigns the length of #_ to garage. In the call to the testDriveCars method you pass a single arg, hence the length is one, hence your error message about "1".
You could write
my ( $garage ) = #_;
or perhaps
my $garage = shift;
instead.
There's a missing semicolon in the posting too - after the assignment of #garage.
See perldoc perlsub for the details.

Related

How to iterate through an Array of hashes in Perl?

I have the following array:
ifNameList -> $VAR1 = [
{
'VALUE' => ' gpon_olt-1/1/1',
'ASN1' => '285278465'
},
{
'VALUE' => ' gpon_olt-1/1/2',
'ASN1' => '285278466'
},
{
'VALUE' => ' gpon_olt-1/1/3',
'ASN1' => '285278467'
},
{
'VALUE' => ' gpon_olt-1/1/4',
'ASN1' => '285278468'
},
{
'VALUE' => ' gpon_olt-1/1/5',
'ASN1' => '285278469'
},
]
I need to iterate through this array of hashes comparing the "VALUE" field of each hash, until it matches and do some action.
I've already made the following code, but its not working. What I'm doing wrong?
sub GetIfIndexFromName{
my $ifName = shift;
my #ifList = shift;
my $index;
for (#ifList){
my %interfaceHash = %$_;
# Just trims any blank space on the string:
$interfaceHash->{"VALUE"} =~ s/^\s+|\s+$//g;
if($interfaceHash->{"VALUE"} eq $ifName){
print "trimmed interface name-> ".$interfaceHash->{"VALUE"}."\n\n";
$index = $interfaceHash->{"ASN1"};
}
}
print "Returning index value: ".$index;
return $index;
}
Two errors.
Problem 1: Wrong variable
ALWAYS use use strict; use warnings;. It would have found this error:
# Access the `VALUE` element of the hash referenced by `$interfaceHash`.
$interfaceHash->{"VALUE"}
You have no variable named $interfaceHash.
There are three ways to fix this:
for ( #ifList ) {
my %interfaceHash = %$_;
... $interfaceHash{ VALUE } ...
}
for my $interfaceHash ( #ifList ) {
... $interfaceHash->{ VALUE } ...
}
The latter is recommended. It avoids creating a copy of the hash, which involves create a number of temporary scalars. This is all useless work.
Problem 2: Incorrect parameter retrieval
The following is wrong:
my #ifList = shift;
shift returns a scalar. There's absolutely no point in using an array to hold exactly one scalar at all times.
sub GetIfIndexFromName {
my $ifName = shift;
my $ifList = shift;
for ( #$ifList ) {
...
}
}
# Pass a reference to the array.
GetIfIndexFromName( $ifName, $VAR1 )
sub GetIfIndexFromName {
my $ifName = shift;
my #ifList = #_;
for ( #ifList ) {
...
}
}
# Pass each element of the array.
GetIfIndexFromName( $ifName, #$VAR1 )
The former convention is more efficient, but the latter can create cleaner code in the caller. Probably not in your program, though.
How I'd write this:
use strict;
use warnings;
use feature qw( say );
use List::Util qw( first );
sub trim_inplace { $_[0] =~ s/^\s+|\s+\z//g; }
my #ifList = ...;
my $ifName = ...;
trim_inplace( $_->{ VALUE } ) for #ifList;
my $match = first { $_->{ VALUE } eq $ifName } #ifList
or die( "Interface not found.\n" );
my $asn1 = $match->{ ASN1 };
say $asn1;

Passing hash slice to subroutine expecting array reference

Consider the following perl script:
use strict;
use warnings;
use Data::Dumper;
# Expects an array reference and an undef replacement value
sub undef_to_value(\#$) {
my $array_ref = shift(#_);
my $default = shift(#_);
# Map each element in the array referenced by $array_ref to...
# If the element is defined, itself
# Otherwise, the $default value
return map {
defined($_) ? $_ : $default
} #{ $array_ref };
}
my %grades = ("Alice" => 100, "Bob" => 85, "Carol" => 92);
my #students = ("Alice", "Bob", "Eve");
The following code works as expected:
my #student_grades = #grades{ #students };
#student_grades = undef_to_value(#student_grades, 0);
print Dumper(\#student_grades);
# $VAR1 = [
# 100,
# 85,
# 0
# ];
However, trying to pass a hash slice results in Type of arg 1 to main::undef_to_value must be array (not hash slice):
my #student_grades = undef_to_value( #grades{ #students }, 0 );
How is it that a hash slice can be coaxed into an array with assignment, but not during a subroutine call?
Is there any way to get the failing example to work as a single assignment?
How is it that a hash slice can be coaxed into an array with assignment
It's not. A hash slice in list context evaluates to a number of scalars, and the list assignment operator is perfectly happy with that.
In other words,
#L{qw( a b )} = #R{qw( a b )};
is equivalent to
($L{a}, $L{b}) = ($R{a}, $R{b});
As your comment says, undef_to_value expects an array reference. The prototype provides this if you provide an array, but you are providing" an hash slice instead. That's not a type a variable, so you can't take a reference to it[1].
Just accept scalars instead:
sub undef_to_value {
my $default = shift(#_);
return map { $_ // $default } #_;
}
my #student_grades = undef_to_value(0, #grades{ #students });
Of course, you could simply use
my #student_grades = map { $_ // 0 } #grades{ #students };
\#h{LIST} is equivalent to map { \$_ } #h{LIST}.
You can fool the prototype by referencing and then dereferencing an arbitrary list
undef_to_value( #{[#grades{ #students }]}, 0 );
but this will only modify a copy of your input, so it's not that helpful.
undef_to_value( #{[#grades{ #students }]}, 0 );
print Dumper([#grades{#students}]);
---
$VAR1 = [
100,
85,
undef
];
Fortunately, your undef_to_value function also returns the set of updated values, so you can say
#grades{#students} = undef_to_value( #{[#grades{ #students }]}, 0 );
print Dumper(\%grades);
---
$VAR1 = {
'Bob' => 85,
'Carol' => 92,
'Eve' => 0,
'Alice' => 100
};

Dereferencing an array reference from a nested Perl hash

I hope I've stated that subject correctly. I have a hash of hashes that I've built from reading a file. The outer hash is groups, then the inner hash is parameters within that group. Each parameter value can either be a scalar or array, and the arrays can start at either zero or one.
I've written a subroutine that returns the value of a parameter. The calling function has to figure out whether the returned value is a scalar or an array. Works fine for scalars. Returns a reference to an array for array values (looks like ARRAY(0x004f00)). Using Data::Dumper spits out data that looks like an array, but I can't figure out how to dereference it in the code. Can someone point to what I'm doing wrong?
%HoH = (
flintstones => {
husband => "fred",
possessions => [ undef, "car", "record player", "rock" ],
pal => "barney",
pets => [ "bird", "dinosaur" ],
},
);
get_value("possessions");
sub get_value {
my $what_i_want = shift;
#groups = keys(%HoH);
foreach my $group ( #groups ) {
foreach my $param ( keys( %{ HoH {group} } ) ) {
if ( $param eq $what_i_want ) {
return $HoH{$group}{$param};
}
}
}
}
The caller assigns the return value to an array, #return, so in the case of a scalar it should put the value in $return[0].
In the case of an array, it should populate the array. When I call Dumper, it prints out scalars in single quotes and arrays in square brackets, as it should. However, when I use scalar(#return) to check the size of the array, it returns 1.
I've tried dereferencing the return statement using square brackets at the end to see if I could even get a scalar returned, but no luck.
You don't show the subroutine being called in context, but a quick fix would be to put this after the call
#return = #{ $return[0] } if ref $return[0]
Update
You're missing the point of hashes. You can access the correct element of the parameter hash by using $what_i_want as a hash key
I suggest you change your subroutine code to look like this
for my $group ( keys %HoH ) {
my $ret = $HoH{$group}{$what_i_want};
return unless defined $ret;
return ref $ret ? #$ret : $ret;
}
That way it will never return a reference
Update 2
Here's your complete program modified as I suggested
use strict;
use warnings 'all';
my %HoH = (
flintstones => {
husband => "fred",
possessions => [ undef, "car", "record player", "rock" ],
pal => "barney",
pets => [ "bird", "dinosaur" ],
},
);
my #return = get_value('possessions');
use Data::Dump;
dd \#return;
sub get_value {
my ($wanted) = #_;
for my $group ( keys %HoH ) {
my $ret = $HoH{$group}{$wanted};
if ( defined $ret ) {
return ref $ret ? #$ret : $ret;
}
}
return;
}
output
[undef, "car", "record player", "rock"]

Array in value of hash perl

Is it possible to assign the reference of an array as the value in the key : value pair of a hash table in perl?
Yes it is. Create a reference to the array by using backslash:
$hash{key} = \#array;
Note that this will link to the actual array, so if you perform a change such as:
$array[0] = "foo";
That will also mean that $hash{key}[0] is set to "foo".
If that is not what you want, you may copy the values by using an anonymous array reference [ ... ]:
$hash{key} = [ #array ];
Moreover, you don't have to go through the array in order to do this. You can simply assign directly:
$hash{key} = [ qw(foo bar baz) ];
Read more about making references in perldoc perlref
Yes. See http://perlmonks.org/?node=References+quick+reference for some basic rules for accessing such data structures, but to create it, just do one of these:
%hash = ( 'somekey' => \#arrayvalue );
$hash{'somekey'} = \#arrayvalue;
%hash = ( 'somekey' => [ ... ] );
use Data::Dumper; #name=('5/17',
'5/17','5/17','5/17','5/17','5/17','5/17','5/17'); #status_flags=('U
H L','U C','U H L','U C','U C','U H L','U C', 'U H L');
#ip_address=('192.168.0.11','192.168.0.2','192.168.0.13','192.168.0.0','192.168.0.3','192.168.0.12','192.168.0.4','192.168.0.14'); #dp_id=('0','0','0','0','0','0','0','0');
#ip_prefix_length=('32','32','32','24', '32', '32','32','32');
for ($value=0;$value<=5;$value++) {
$keyvals{'Response'}{'brocade-extension-ip-route'}{'extension-ip-route'}={'name'=>"$name[$value]"};
$keyvals{'Response'}{'brocade-extension-ip-route'}{'extension-ip-route'}={'dp-id'=>"$dp_id[$value]"};
$keyvals{'Response'}{'brocade-extension-ip-route'}{'extension-ip-route'}={'ip-address'=>"$ip_address[$value]"};
$keyvals{'Response'}{'brocade-extension-ip-route'}{'extension-ip-route'}={'ip-prefix-length'=>"$ip_prefix_length[$value]"};
$keyvals{'Response'}{'brocade-extension-ip-route'}{'extension-ip-route'}={'ip-gateway'=>'*'};
}
print Dumper \%keyvals;
Each array value assign into hash value. $var1= {
'Response' => {
'extension-ip-route' => {
'status-flags' => 'U H L '
,
'ip-gateway' => '*',
'name' => '0/2',
'ip-address' => '192.168.20.11',
'dp-id' => '0',
'ip-prefix-length'=>'32'
}
}
};

How do I reference a Perl hash in an array in a hash?

This is the code snippet I am working with:
my %photo_details = (
'black_cat' => (
('size' => '1600x1200', 'position' => -25),
('size' => '1280x1024', 'position' => 25),
('size' => '800x600', 'position' => 0),
),
'race_car' => (
('size' => '1600x1200', 'position' => 10),
('size' => '800x600', 'position' => 5),
),
);
my $photo = 'black_cat';
foreach my $photo_detail ($photo_details{$photo})
{
my $size = $photo_detail{'size'};
my $position = $photo_detail{'position'};
print ("size = $size, position = $position\n");
}
What I am expecting to get is:
size = 1600x1200, position = -25
size = 1280x1024, position = 25
size = 800x600, position = 0
What I do get is:
Use of uninitialized value $size in concatenation (.) or string at C:\Test.pl line 23.
Use of uninitialized value $position in concatenation (.) or string at C:\Test.pl line 23.
size = , position =
The foreach statement is clearly wrong as not only are there no values for $size and $position, it has only gone through the loop once instead of three times. I have tried all sorts of variants of variable prefixes and found none that work.
What am I doing wrong?
Here is some updated code, with an explanation below:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %photo_details = (
'black_cat' => [
{'size' => '1600x1200', 'position' => -25},
{'size' => '1280x1024', 'position' => 25},
{'size' => '800x600', 'position' => 0},
],
'race_car' => [
{'size' => '1600x1200', 'position' => 10},
{'size' => '800x600', 'position' => 5},
],
);
print Dumper( %photo_details );
foreach my $name ( keys %photo_details ) {
foreach my $photo_detail ( #{ $photo_details{$name} } ) {
my $size = $photo_detail->{'size'};
my $position = $photo_detail->{'position'};
print Dumper( $photo_details{$photo} );
print ("size = $size, position = $position\n");
}
}
I've replaced some of your parentheses with square and curly brackets. In Perl, square brackets give you a reference to an anonymous array, and curly brackets denote a reference to an anonymous hash. These are called anonymous because there's no explicit variable name for the anonymous array or hash.
As Perl data structures make you store a reference to a hash rather than the actual hash, you need these to construct the references. You can do this in two steps like this:
my #array = ( 1, 2, 3 );
my $array_ref = \#array;
my %hash = ( 'one' => 1, 'two' => 2, 'three' => 3 );
my $hash_ref = \%hash_ref;
To get data out of $array_ref and $hash_ref, you need the -> operator:
print $array_ref->[0], "\n";
print $hash_ref->{one}, "\n";
You don't need the quotes inside of the {} when referencing a hash key, although some people consider quotes on a hash key to be good practice.
I added an example of iteration over the entire data structure as an example rather than just looking at one reference. Here's the first line:
foreach my $name ( keys %photo_details ) {
The keys method returns all of the keys in a hash, so that you can get them in order. The next line iterates over all of the photo_detail hashrefs in %photo_details:
foreach my $photo_detail ( #{ $photo_details{$photo} } ) {
The #{ $photo_details{$photo} } de-references the reference $photo_details{$photo} into an array, which you can iterate over it with foreach.
The last thing that I added is a call to Data::Dumper, a very useful module distributed with Perl that prints out data structures for you. This is very handy when building up data structures like this, as is its closely related cousin Data::Dumper::Simple. This module is unfortunately not distributed with Perl, but I prefer its output as it includes variable names.
For some further reading about how to build up complex data structures using references, check out perlreftut.
First of all, always start every script or module with:
use strict;
use warnings;
You will get more warning messages and sooner, which greatly helps debugging.
I cannot duplicate your error: when I put that code into a file and run it with no additional flags, I get: size = , position =. There is no $size variable in the code you printed, so the error message does not match.
Nevertheless, you are declaring your data structures incorrectly. Hashes and arrays can
only contain scalar values, not lists: so if you want to nest an array or
a hash, you need to make it a reference. See perldoc perldata, perldoc perldsc
and perldoc perlreftut for more about data structures and references.
my %photo_details = (
black_cat => [
{ size => '1600x1200', position => -25 },
{ size => '1280x1024', position => 25 },
{ size => '800x600', position => 0 },
],
race_car => [
{ size => '1600x1200', position => 10 },
{ size => '800x600', position => 5 },
],
);
foreach my $photo_detail (#{$photo_details{black_cat}})
{
my $size = $photo_detail->{size};
my $position = $photo_detail->{position};
print ("size = $size, position = $position\n");
}
There's really only one thing you have to worry about, and that's the top level of the data structure. After that, you just use the right indexing syntax for each level:
If you have a regular hash, you access the key that you want then line up the additional indices for each level after it:
%regular_hash = ...;
$regular_hash{$key}[$index]{$key2};
If you have a reference, you do almost the same thing, but you have to start off with the initial dereference with an arrow, ->, after the top-level reference. After that it's the same indexing sequence:
$hash_ref = ...;
$hash_ref->{$key}[$index]{$key2};
For all of the details, see Intermediate Perl where we explain reference syntax.

Resources