How to iterate through an Array of hashes in Perl? - arrays

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;

Related

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

Generating a unordered list hash from a array

I am trying to use hashes to generate an unordered list that i can further use in a jstree. But this array has to be generated only from an array that has been passed thru .
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
I want the output to look something like
$data = {
"New Order" => {
"Recurring Order" =>{
Previously cancelled Order = 1
}
}
};
I can simply do
my $data{$array[0]}{$array[1]}{$array[2]} = 1
but the array can be of n variables, so it becomes a bit more complicated than that. I am thinking of recursion, but i have been sitting here for the last hour trying to figure that out
This will generate the data structure as you have defined it. Not sure why you'd want it though.
my #input = ( "New Order","Recurring Order","Previously Cancelled Order");
my $data = 1;
$data = {$_ => $data} for reverse #input;
use Data::Dump;
dd $data;
If you're just wanting to randomize your array, then use List::Util;
use List::Util qw(shuffle);
my #newOrder = shuffle #input;
sub recursive {
my $v = shift #_;
return #_>1 ? { $v => recursive(#_) } : { $v => #_ };
}
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
use Data::Dumper; print Dumper recursive(#array, 1);
output
$VAR1 = {
'New Order' => {
'Recurring Order' => {
'Previously Cancelled Order' => 1
}
}
};

Hashes array elements copy

My array of hashes:
#cur = [
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
];
Expected result:
#curnew = ('9872', '9871');
Any simple way to get only the values of the first hash element from
this and assign it to an array?
Mind that hashes are unordered, so I take the word first to mean lexicographically first.
map { # iterate over the list of hashrefs
$_->{ # access the value of the hashref
(sort keys $_)[0] # … whose key is the first one when sorted
}
}
#{ # deref the arrayref into a list of hashrefs
$cur[0] # first/only arrayref (???)
}
The expression returns qw(9872 9871).
Assigning an arrayref to an array as in #cur = […] is probably a mistake, but I took it at face value.
Bonus perl5i solution:
use perl5i::2;
$cur[0]->map(sub {
$_->{ $_->keys->sort->at(0) }
})->flatten;
The expression returns the same values as above. This code is a bit longer, but IMO more readable because the flow of execution goes strictly from top to bottom, from left to right.
First your array have to be defined as
my #cur = (
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
);
Note the parenthesis
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dump qw(dump);
my #cur = (
{
'A' => '9872',
'B' => '1111'
},
{
'A' => '9871',
'B' => '1111'
}
);
my #new;
foreach(#cur){
push #new, $_->{A};
}
dump #new;
use Data::Dumper;
my #hashes = map (#{$_}, map ($_, $cur[0]));
my #result = map ($_->{'A'} , #hashes);
print Dumper \#result;

How to push an Array of Hashes to Array of Hashes?

I have this script
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %acc = ();
&insert_a(\%acc, 11);
&insert_p(\%acc, 111);
print Dumper %acc;
sub insert_a() {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
sub insert_p() {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
push $acc_ref->{"$_[0]"}{"c"}, #AoH;
}
where I am trying to insert AoH into c which also is an AoH, but I am getting
Type of arg 1 to push must be array (not hash element) at ./push.pl line 36, near "#AoH;"
Execution of ./push.pl aborted due to compilation errors.
Any ideas how to do that?
The specific problem is that you can only push to an array, so you first need to dereference the array, and then, since it's in a larger data structure, you want to set its value to a reference.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %acc = ();
# don't use & to call subs; that overrides prototypes (among other things)
# which you won't need to worry about, because you shouldn't be using
# prototypes here; they're used for something else in Perl.
insert_a(\%acc, 11);
insert_p(\%acc, 111);
# use \%acc to print as a nice-looking hashref, all in one variable
print Dumper \%acc;
# don't use () here - that's a prototype, and they're used for other things.
sub insert_a {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
# same here
sub insert_p {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
# You need to dereference the first array, and pass it a reference
# as the second argument.
push #{ $acc_ref->{"$_[0]"}{"c"} }, \#AoH;
}
I'm not quite sure that the resulting data structure is what you intended, but now that you have the program working and can see the resulting structure, you can modify it to get what you need.
Hash values are always scalar, so to store an array in a hash you need to store a reference to the array. Try using the following line, where the hash value is dereferenced to an array.
push #{ $acc_ref->{$_[0]}->{'c'} }, #AoH;
Do it like,
push #{$acc_ref->{"$_[0]"}->{"c"}}, #AoH;
or you can try $acc_ref->{"$_[0]"}->{"c"} = \#AoH;
Your script,
use strict;
use warnings
use Data::Dumper;
my %acc = ();
&insert_a(\%acc, 11);
&insert_p(\%acc, 111);
print Dumper %acc;
sub insert_a() {
my $acc_ref = shift;
$acc_ref->{"$_[0]"} = {
a => -1,
b => -1,
c => [ { }, ],
}
}
sub insert_p() {
my $acc_ref = shift;
my #AoH = (
{
d => -1,
e => -1,
}
);
push #{$acc_ref->{"$_[0]"}->{"c"}}, #AoH;
}
Output:
$VAR1 = '11';
$VAR2 = {
'c' => [
{}
],
'a' => -1,
'b' => -1
};
$VAR3 = '111';
$VAR4 = {
'c' => [
{
'e' => -1,
'd' => -1
}
]
};

References in Perl: Array of Hashes

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.

Resources