I want to modify an array of hashes by a sub function, therefore I want to handover the array by reference, de-reference in the sub function and modify it furthermore.
After this modification, the array shall hold the modified values instantly, I don't want to explicitely return the modified hash (want to work on the original array).
Unfortunately I do not succeed with that. There are many web hints concerning access to references of array of hashes, but I couldn't find one which manipulates the array.
my #array_of_hashes = ( {name => "Alice"},
{name => "Bob"} );
my $myhashref = \%{$array_of_hashes[0]}; # This holds a ref to {name=>"Alice"}
my %myhash = %{$myhashref}; # De-reference, shall be the Hash to work on
print $myhash{name} . "\n"; # This shows Alice
$myhash{age}=32; # Want to add 'age' to the Alice Hash, does not work
This modified hash does not show {age}. When you have a look at #array_of_hashes with print Data::Dump::dump(#array_of_hashes) the line $myhash{age}=32; has no impact on #array_of_hashes.
How can I hand over a reference to e.g. the first element of #array_of_hashes to a function and how to I have to dereference it in the function in order to be able to modify the hash within #array_of_hashes?
You said: I want to modify an array of hashes by a sub function
If I understand right, something like the following could work:
use 5.014;
use warnings;
use Data::Dumper;
my #aoh = (
{name => "Alice"},
{name => "Bob"}
);
do_some(\#aoh); #pass arrayref
$aoh[1]->{text} = 'huhu';
say Dumper \#aoh;
say "$aoh[1]->{name} has age $aoh[1]->{age} and says $aoh[1]->{text}";
sub do_some {
my $ar = shift;
for my $hr (#$ar) { #for each array element
$hr->{age} = int rand 100;
}
}
# however (IMHO)
# using arrayref from the beginning is more cleaner
my $aohr = [
{name => "Alice"},
{name => "Bob"}
];
do_some($aohr);
$aohr->[0]->{text} = 'juju';
say Dumper $aohr;
say "$aohr->[0]->{name} has age $aohr->[0]->{age} and says $aohr->[0]->{text}";
#could use the shortened form
#say "$aohr->[0]{name} has age $aohr->[0]{age} and says $aohr->[0]{text}";
the above produces for example:
$VAR1 = [
{
'age' => 31,
'name' => 'Alice'
},
{
'age' => 10,
'text' => 'huhu',
'name' => 'Bob'
}
];
Bob has age 10 and says huhu
$VAR1 = [
{
'name' => 'Alice',
'age' => 94,
'text' => 'juju'
},
{
'name' => 'Bob',
'age' => 57
}
];
Alice has age 94 and says juju
You already created a ref to each hash when you create the array. then you defrencing the hash and allocating it to a new hash varible.
my %myhash = %{$myhashref}; # De-reference, shall be the Hash to work on
So you now have a new hash which was created as a copy of the alice hash. However the new hash and the alice hash are seperate. you then modify the new hash works ok but it will not be reflected in the alice hash as they are seperate. instead you should modify the existing hash ref. For example try below.
use strict;
use warnings;
use Data::Dumper;
my #array_of_hashes = ( {name => "Alice"},
{name => "Bob"} );
print $array_of_hashes[0]->{'name'}, "\n";#this shows alice
$array_of_hashes[0]->{'age'}=32; # Want to add 'age' to the Alice Hash, does not work
print Dumper \#array_of_hashes;
Related
If I wanted to add a new hash to all the arrays in the mother_hash using a loop, what would be the syntax?
My hash:
my %mother_hash = (
'daughter_hash1' => [
{
'e' => '-4.3',
'seq' => 'AGGCACC',
'end' => '97',
'start' => '81'
}
],
'daughter_hash2' => [
{
'e' => '-4.4',
'seq' => 'CAGT',
'end' => '17',
'start' => '6'
},
{
'e' => '-4.1',
'seq' => 'GTT',
'end' => '51',
'start' => '26'
},
{
'e' => '-4.1',
'seq' => 'TTG',
'end' => '53',
'start' => '28'
}
],
#...
);
If you have a hash of arrays of hashes and want to add a new hash to
the end of each of the arrays, you can do:
push #{ $_ }, \%new_hash for (values %mother_hash);
This loop iterates over the values of %mother_hash (which are array refs in this case) and setting $_ for each iteration. Then in each iteration, we push the reference to the new hash %new_hash to the end of that array.
First I would point out the daughter hashes aren't hashes but arrays of anonymous hashes. To add another daughter hash:
$mother_hash{daughter_hash3} = [ { %daughter_hash3 } ];
This creates an anonymous array that contains an anonymous hash with the contents of %daughter_hash3.
For a loop:
$mother_hash{$daughter_hash_key} = [ { %daughter_hash } ];
where $daughter_hash_key is a string contain the key for the %mother_hash and %daughter_hash is the hash to add.
To add another hash to a daughter array with key $daughter_hash_key:
push #{ $mother_hash{$daughter_hash_key} }, { %daughter_hash };
I know ti's complicated but I suggest you use Data::Dumper to dump the contents of %mother_hash each time thru the loop to see if it grows correctly.
use Data::Dumper;
print Dumper \%mother_hash;
See perldoc Data::Dumper for details..
Data::Dumper is a standard module that comes with Perl. For a list of standard modules, see perldoc perlmodlib.
mother_hash is a hash of arrays of hashes.
To add another top-level array of hashes.
%mother_hash{$key} = [ { stuff }, { stuff } ];
To add another entry to an existing array
push #{%mother_hash{'key'}} { stuff };
To add another entry to the hash in the embedded array
%{#{%mother_hash{'top_key'}}[3]}{'new_inner_key'} = value;
When confused and attempting to match up the "types" of hash / array / scalar containing a hash reference / array reference, you can use the following technique
use Data::Dumper;
$Data::Dumper::Terse = 1;
printf("mother_hash reference = %s\n", Dumper(\%mother_hash));
printf("mother_hash of key 'top_key' = %s\n", Dumper(%mother_hash{top_key}));
and so on to find your way through a large data structure and validate that you are narrowing down to the region you want to access or alter.
I have this big array that I need to break down and only display specific elements within it that match a criteria.
My array looks like this.
[
{
:id => 9789,
:name => "amazing location",
:priority => 1,
:address_id => 12697,
:disabled => false
},
{
:id => 9790,
:name => "better location",
:priority => 1,
:address_id => 12698,
:disabled => false
},
{
:id => 9791,
:name => "ok location",
:priority => 1,
:address_id => 12699,
:disabled => true
}
]
What I need is to only display the elements within this array that have disabled set to true.
However when I try this, I get the error stating no implicit conversion of Symbol into Integer
array.map do |settings, value|
p hash[:disabled][:true]
end
I'm wondering if there is another way, or if there is a way to do this. If anyone could take a look, I would greatly appreciate it.
By providing two arguments to #map on an array, you're actually getting the first hash and then nil. When in reality you just want to loop for each and select those where disabled is true. You can do that instead with Array#select which will filter all elements of the array where the block returns a truthy value:
print array.select { |hash| hash[:disabled] }
=> [{:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}]
You can try this with a short each or select.
a.each { |k,_v| puts k if k[:disabled] == true }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}
This iterates over each element (hash) inside the array you have and checks if the value of the key disabled on each value is true, and puts the key, just for example, you can set it as you want to do.
Or shorter:
puts a.select { |k,_v| k[:disabled] }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}
Your error shows up when you are treating an array or string as a Hash.
In PHP, array keys can be either numbers or strings, whereas in Ruby associative arrays are a separate data type, called a hash.
Here’s a cheatsheet for various foreach variants, translated into idiomatic Ruby:
Looping over a numeric array (PHP) :
<?php
$items = array( 'orange', 'pear', 'banana' );
# without indexes
foreach ( $items as $item ) {
echo $item;
}
# with indexes
foreach ( $items as $i => $item ) {
echo $i, $item;
}
Looping over an array (Ruby) :
items = ['orange', 'pear', 'banana']
# without indexes
items.each do |item|
puts item
end
# with indexes
items.each_with_index do |item, i|
puts i, item
end
Looping over an associative array (PHP) :
<?php
$continents = array(
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
);
# without keys
foreach ( $continents as $continent ) {
echo $continent;
}
# with keys
foreach ( $continents as $slug => $title ) {
echo $slug, $title;
}
Looping over a hash (Ruby):
continents = {
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
}
# without keys
continents.each_value do |continent|
puts continent
end
# with keys
continents.each do |slug, title|
puts slug, title
end
In Ruby 1.9 hashes were improved so that they preserved their internal order. In Ruby 1.8, the order in which you inserted items into a hash would have no correlation to the order in which they were stored, and when you iterated over a hash, the results could appear totally random. Now hashes preserve the order of insertion, which is clearly useful when you are using them for keyword arguments in method definitions. (thanks steenslag for correcting me on this)
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
};
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'
}
}
};
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.