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
};
Related
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;
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"]
I'm trying to grep a value in Perl:
#array = ['hello', 'world'];
if (grep(/hello/i, #array) {
# do something
}
For some reason my code isn't picking this up. Perhaps there's another way I can do this.
the Array itself is inside a hash:
hash => {
array => ['hello', 'world'],
value => 'feature',
}
You're building your array wrong. This line creates a one-element array, and that element is an array reference.
#array = ['hello', 'world'];
When you grep over that one-element array, that one array reference doesn't match /hello/i.
What you want is:
#array = ('hello', 'world');
After
#array = ['hello', 'world'];
you have:
$ perl -MData::Dumper -e '#array = ['hello', 'world']; print Dumper \#array'
$VAR1 = [
[
'hello',
'world'
]
];
That is #array contains a reference to an anonymous array containing the strings 'hello' and 'world'.
Then, in grep, you evaluate this reference as a string. Therefore, your grep does a single comparison along the lines of
'ARRAY(0x7fa0e38032b8)' =~ /hello/i;
Clearly, that is not going to match.
#!/usr/bin/env perl
use strict;
use warnings;
my %hash = (array => ['hello', 'world']);
if (grep /hello/i, #{ $hash{array} }) {
print "\#array contains 'hello'\n";
}
The usage is indeed
if (grep(/hello/i, #array)) { ... }
But according to the comments, you don't have a named array. You have a reference to an array. As such, you replace #array with an array dereference.
if (grep(/hello/i, #$array_ref)) { ... }
That's short for
if (grep(/hello/i, #{ $array_ref })) { ... }
Since your reference comes from a hash, you could also do
if (grep(/hello/i, #{ $hash{$key} })) { ... }
The Array itself is inside a hash:
hash => {
array => ['hello', 'world'],
value => 'feature',
}
Use Data::Dumper to see how exactly you've defined your structure:
use Data::Dumper;
use feature qw(say); # Highly recommend "say" when using Data::Dumper!
my %hash = (
array => ['hello', 'world'],
value => 'feature',
);
...
say Dumper \%hash;
And see what prints out. (Note the backslash in front, so you're passing in a single reference_ and not a list of values).
What you'll see is something like this:
$var = {
'array' => [
'hello',
'world',
]
}
That array isn't just an array, it's a reference to an array. You need to dereference it to get it to work:
if ( grep {/hello/i } #{ $hash->{array} } )
Or...
my #array = #{ $hash->{array} );
if ( grep { /hello/i } #array;
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
}
]
};
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.