Perl array and hash manipulation using map - perl

I have the following test code
use Data::Dumper;
my $hash = {
foo => 'bar',
os => 'linux'
};
my #keys = qw (foo os);
my $extra = 'test';
my #final_array = (map {$hash->{$_}} #keys,$extra);
print Dumper \#final_array;
The output is
$VAR1 = [
'bar',
'linux',
undef
];
Shouldn't the elements be "bar, linux, test"? Why is the last element undefined and how do I insert an element into #final_array? I know I can use the push function but is there a way to insert it on the same line as using the map command?
Basically the manipulated array is meant to be used in an SQL command in the actual script and I want to avoid using extra variables before that and instead do something like:
$sql->execute(map {$hash->{$_}} #keys,$extra);

$extra is being passed through the map and since there's no entry in the hash with the key test the value is undef. Put parentheses around the map to force the parsing:
$sql->execute((map {$hash->{$_}} #keys),$extra);

You can also use a hash slice to avoid looping with map:
my #final_array = (#$hash{#keys}, $extra);

Related

Why can't I reference a (particular) Perl array as I'd expect?

I am attempting to use the following to iterate through an array of arrays:
foreach my $elem ( #{ $mastermap } )
{
say $elem;
say $elem[0];
say Dumper($elem);
}
(All just debug output at this point, not what I actually want to do with the array data.)
The output I'm getting (repeated for each loop iteration) is something like:
ARRAY(0x55dabc740cc0)
Use of uninitialized value in say at test.pl line 39.
$VAR1 = [
'bob',
'*',
'1492',
'1492',
'machine acct',
'/var/bob',
'/bin/false'
];
So $elem is an array (also tried treating it as a hash, which was wrong), and Dumper can output the contents of the array, but $elem[0] is undefined? Please tell me what I'm misunderstanding about arrays (probably quite a bit). In case it helps, $mastermap is (I think) an array of arrays, read in using Text::CSV as follows:
my $mastermap = csv ({ in => $passwd, sep_char => ":", quote_char => "#" });
where $passwd is more or less a copy of /etc/passwd.
$elem[0] tries to access to the 1st item in the array #elem. What you actually want to is to access the 1st item in the array reference $elem. The correct syntax to do so is:
$elem->[0]
or
$$elem[0]
You should always add use strict; and use warnings; to the beginning of your Perl scripts/programs. Doing so would have produced the following warning:
Global symbol "#elem" requires explicit package name (did you forget to declare "my #elem"?) at...

Is it possible to push a key-value pair directly to hash in perl?

I know pushing is only passible to array, not hash. But it would be much more convenient to allow pushing key-value pair directly to hash (and I am still surprise it is not possible in perl). I have an example:
#!/usr/bin/perl -w
#superior words begin first, example of that word follow
my #ar = qw[Animals,dog Money,pound Jobs,doctor Food];
my %hash;
my $bool = 1;
sub marine{
my $ar = shift if $bool;
for(#$ar){
my #ar2 = split /,/, $_;
push %hash, ($ar2[0] => $ar2[1]);
}
}
marine(\#ar);
print "$_\n" for keys %hash;
Here I have an array, which has 2 words separately by , comma. I would like to make a hash from it, making the first a key, and the second a value (and if it lacks the value, as does the last Food word, then no value at all -> simply undef. How to make it in perl?
Output:
Possible attempt to separate words with commas at ./a line 4.
Experimental push on scalar is now forbidden at ./a line 12, near ");"
Execution of ./a aborted due to compilation errors.
I might be oversimplyfing things here, but why not simply assign to the hash rather than trying to push into it?
That is, replace this unsupported expression:
push %hash, ($ar2[0] => $ar2[1]);
With:
$hash{$ar2[0]} = $ar2[1];
If I incoporate this in your code, and then dump the resulting hash at the end, I get:
$VAR1 = {
'Food' => undef,
'Money' => 'pound',
'Animals' => 'dog',
'Jobs' => 'doctor'
};
Split inside map and assign directly to a hash like so:
my #ar = qw[Animals,dog Money,pound Jobs,doctor Food];
my %hash_new = map {
my #a = split /,/, $_, 2;
#a == 2 ? #a : (#a, undef)
} #ar;
Note that this can also handle the case with more than one comma delimiter (hence splitting into a max of 2 elements). This can also handle the case with no commas, such as Food - in this case, the list with the single element plus the undef is returned.
If you need to push multiple key/value pairs to (another) hash, or merge hashes, you can assign a list of hashes like so:
%hash = (%hash_old, %hash_new);
Note that the same keys in the old hash will be overwritten by the new hash.
We can assign this array to a hash and perl will automatically look at the values in the array as if they were key-value pairs. The odd elements (first, third, fifth) will become the keys and the even elements (second, fourth, sixth) will become the corresponding values. check url https://perlmaven.com/creating-hash-from-an-array
use strict;
use warnings;
use Data::Dumper qw(Dumper);
my #ar;
my %hash;
#The code in the enclosing block has warnings enabled,
#but the inner block has disabled (misc and qw) related warnings.
{
#You specified an odd number of elements to initialize a hash, which is odd,
#because hashes come in key/value pairs.
no warnings 'misc';
#If your code has use warnings turned on, as it should, then you'll get a warning about
#Possible attempt to separate words with commas
no warnings 'qw';
#ar = qw[Animals,dog Money,pound Jobs,doctor Food];
# join the content of array with comma => Animals,dog,Money,pound,Jobs,doctor,Food
# split the content using comma and assign to hash
# split function returns the list in list context, or the size of the list in scalar context.
%hash = split(",", (join(",", #ar)));
}
print Dumper(\%hash);
Output
$VAR1 = {
'Animals' => 'dog',
'Money' => 'pound',
'Jobs' => 'doctor',
'Food' => undef
};

How to manipulate a hash-ref with Perl?

Take a look at this code. After hours of trial and error. I finally got a solution. But have no idea why it works, and to be quite honest, Perl is throwing me for a loop here.
use Data::Diff 'Diff';
use Data::Dumper;
my $out = Diff(\#comparr,\#grabarr);
my #uniq_a;
#temp = ();
my $x = #$out{uniq_a};
foreach my $y (#$x) {
#temp = ();
foreach my $z (#$y) {
push(#temp, $z);
}
push(#uniq_a, [$temp[0], $temp[1], $temp[2], $temp[3]]);
}
Why is it that the only way I can access the elements of the $out array is to pass a hash key into a scalar which has been cast as an array using a for loop? my $x = #$out{uniq_a}; I'm totally confused. I'd really appreciate anyone who can explain what's going on here so I'll know for the future. Thanks in advance.
$out is a hash reference, and you use the dereferencing operator ->{...} to access members of the hash that it refers to, like
$out->{uniq_a}
What you have stumbled on is Perl's hash slice notation, where you use the # sigil in front of the name of a hash to conveniently extract a list of values from that hash. For example:
%foo = ( a => 123, b => 456, c => 789 );
$foo = { a => 123, b => 456, c => 789 };
print #foo{"b","c"}; # 456,789
print #$foo{"c","a"}; # 789,123
Using hash slice notation with a single element inside the braces, as you do, is not the typical usage and gives you the results you want by accident.
The Diff function returns a hash reference. You are accessing the element of this hash that has key uniq_a by extracting a one-element slice of the hash, instead of the correct $out->{uniq_a}. Your code should look like this
my $out = Diff(\#comparr, \#grabarr);
my #uniq_a;
my $uniq_a = $out->{uniq_a};
for my $list (#$uniq_a) {
my #temp = #$list;
push #uniq_a, [ #temp[0..3] ];
}
In the documentation for Data::Diff it states:
The value returned is always a hash reference and the hash will have
one or more of the following hash keys: type, same, diff, diff_a,
diff_b, uniq_a and uniq_b
So $out is a reference and you have to access the values through the mentioned keys.

assign a hash into a hash

I wish to assign a hash (returned by a method) into another hash, for a given key.
For e.g., a method returns a hash of this form:
hash1->{'a'} = 'a1';
hash1->{'b'} = 'b1';
Now, I wish to assign these hash values into another hash inside the calling method, to get something like:
hash2->{'1'}->{'a'} = 'a1';
hash2->{'1'}->{'b'} = 'b1';
Being new to perl, I'm not sure the best way to do this. But sounds trivial...
Your sub might be:
#!/usr/bin/env perl
use strict;
use warnings;
sub mystery
{
my($hashref) = { a => 'a1', b => 'b1' };
return $hashref;
}
my $hashref1 = mystery;
print "$hashref1->{a} and $hashref1->{b}\n";
my $hashref2 = { 1 => $hashref1 };
print "$hashref2->{1}->{a} and $hashref2->{1}->{b}\n";
One key point is that your notation for accessing the variables with the -> arrow operator is dealing with hash refs, not with plain hashes.
We have a 1st and a 2nd hash:
my %hash1 = (
a => 'a1',
b => 'b1');
my %hash2 = (1 => undef);
We can only assign scalar values to hashes, but this includes references. To take a reference, use the backslash operator:
$hash2{1} = \%hash1;
We can now dereference the values almost as in your example:
print $hash2{1}->{a}; # prints "a1"
Be carefull to use the correct sigil ($#%) as appropriate. Use the sigil of the data type you expect, wich is not neccessarily the type you declared.
"perldoc perlreftut" might be interesting.

Perl "Not an ARRAY reference" error

I'll be glad if someone can enlighten me as to my mistake:
my %mymap;
#mymap{"balloon"} = {1,2,3};
print $mymap{"balloon"}[0] . "\n";
$mymap{'balloon'} is a hash not an array. The expression {1,2,3} creates a hash:
{
'1' => 2,
'3' => undef
}
You assigned it to a slice of %mymap corresponding to the list of keys: ('balloon'). Since the key list was 1 item and the value list was one item, you did the same thing as
$mymap{'balloon'} = { 1 => 2, 3 => undef };
If you had used strict and warnings it would have clued you in to your error. I got:
Scalar value #mymap{"balloon"} better written as $mymap{"balloon"} at - line 3.
Odd number of elements in anonymous hash at - line 3.
If you had used 'use strict; use warnings;' on the top of your code you probably have had better error messages.
What you're doing is creating a hash called mymap. A hash stores data as key => value pairs.
You're then assigning an array reference to the key balloon. Your small code snipped had two issues: 1. you did not addressed the mymap hash, 2. if you want to pass a list, you should use square brackets:
my %mymap;
$mymap{"balloon"} = [1,2,3];
print $mymap{"balloon"}[0] . "\n";
this prints '1'.
You can also just use an array:
my #balloon = (1,2,3);
print $balloon[0] . "\n";
Well, first off, always use strict; use warnings;. If you had, it might have told you about what is wrong here.
Here's what you do in your program:
my %mymap; # declare hash %mymap
#mymap{"balloon"} = {1,2,3}; # attempt to use a hash key on an undeclared
# array slice and assign an anonymous hash to it
print $mymap{"balloon"}[0] . "\n"; # print the first element of a scalar hash value
For it to do what you expect, do:
my %mymap = ( 'balloon' => [ 1,2,3 ] );
print $mymap{'balloon'}[0];
Okay, a few things...
%mymap is a hash. $mymap{"balloon"} is a scalar--namely, the value of the hash %mymap corresponding to the key "balloon". #mymap{"balloon"} is an attempt at what's called a hash slice--basically, you can use these to assign a bunch of values to a bunch of keys at once: #hash{#keys}=#values.
So, if you want to assign an array reference to $mymap{"balloon"}, you'd need something like:
$mymap{"balloon"}=[1,2,3].
To access the elements, you can use -> like so:
$mymap{"balloon"}->[0] #equals 1
$mymap{"balloon"}->[1] #equals 2
$mymap{"balloon"}->[2] #equals 3
Or, you can omit the arrows: $mymap{"balloon"}[0], etc.