Why does perl insert an undef value into my hash? - perl

Let me start off with a simple minimal example:
use strict;
use warnings;
use Data::Dumper;
my %hash;
$hash{count} = 4;
$hash{elems}[$_] = {} for (1..$hash{count});
print Dumper \%hash;
Here is the result (reformatted):
$VAR1 = {
'count' => 4,
'elems' => [undef, {}, {}, {}, {}]
};
I do not understand, why did the first element of $hash{elems} become an undef?
I know there are probably easier ways to do what I am doing, but I am creating these empty hashes so that I can later do my $e = $hash{elems}[$i] and continue to use $e to interact with the element, eg continue the horror of nested structures with $e->{subelems}[0] = 100.

Array indices start at 0 in Perl (and in most programming languages for that matter).
In the 1st iteration of $hash{elems}[$_] = {} for (1..$hash{count});, $_ is 1, and you thus put {} at index 1 of $hash{elems}.
Since you didn't put anything at index 0 of $hash{elems}, it contains undef.
To remedy this, you could use push instead of assigning to specific indices:
push #{$hash{elems}}, {} for 1 .. $hash{count};
push adds items at the end of its first argument. Initially, $hash{elems} is empty, so the end is the 1st index (0).
Some tips:
The parenthesis are not needed in for (1..$hash{count}): for 1 .. $hash{count} works just as well and looks a bit lighter.
You could initialize your hash when you declare it:
my %hash = (
count => 4,
elems => [ map { {} } 1 .. 4 ]
);
Initializing elems with an arrayref of hashrefs is often useless, thanks to autovivification. Simply doing $hash{elems}[0]{some_key} = 42 will create an arrayref in $hash{elems}, a hashref at index 0 in this array, containing the key some_key with value 42.
In some cases though, your initialization could make sense. For instance, if you want to pass $hash{elems} (but not $hash) to a function (same thing if you want to pass $hash{elems}[..] to a function without passing $hash{elems}).

Related

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 splice an array that is in a hash of arrays?

I am populating a data structure like so:-
push #{$AvailTrackLocsTop{$VLayerName}}, $CurrentTrackLoc;
Where $VLayerName is a string like m1, m2, m3, etc., and $CurrentTrackLoc is simply a decimal number. If I use Data::Dumper to print the contents of the hash after it is fully populated, it reveals what I expect, e.g.:-
$VAR1 = {
'm11' => [
'0.228',
'0.316',
'0.402',
'0.576',
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
Now I need to effectively splice the stored list of decimal numbers. I can delete entries like so:-
for (my $i = $c; $i <= $endc; $i++) {
delete $AvailTrackLocsTop{$VLayerName}->[$i];
}
The result is, as expected, a bunch of "undef" entries where numbers used to exist, e.g.:-
$VAR1 = {
'm11' => [
undef,
undef,
undef,
undef,
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
But how can I purge the undef entries so that I see something like this instead?
$VAR1 = {
'm11' => [
'0.750',
'569.458',
'569.544',
'569.718',
'569.892'
]
};
It is important to note that the deletions can be anywhere in the array, e.g. like index 33 and 99 of 100. It is easy to splice arrays outside the context of a hash structure, but I am struggling to manipulate the array when it is embedded inside a large hash.
First, I want to note from the delete documentation:
WARNING: Calling delete on array values is strongly discouraged. The notion of deleting or checking the existence of Perl array elements is not conceptually coherent, and can lead to surprising behavior.
The correct way to set an array element to undef is with the undef function (or just assigning undef to it).
To instead remove the elements, you can use the splice function, it works the same way on nested arrayrefs as on a normal array, you just need to dereference it like you did for push.
splice #{$AvailTrackLocsTop{$VLayerName}}, $c, $endc - $c + 1;
Probably the easiest given where you're at is to rebuild the arrays without the undefs:
$_ = [ grep defined, #$_ ] for values %AvailTrackLocsTop;
Alternatively, instead of a hash of arrays, you could have a hash of hashes, and then deleting will cause them to disappear without simply turning to undef. You'll just lose the order, if that matters.

How do I efficiently create a perl hash of consecutive numbers?

I need to create something like this:
my $valueref = {
1 => 1,
2 => 2,
3 => 3,
4 => 4
};
Based on certain conditions, it might be up to 40, or 50 or 60. In each case it would be consecutive integers, as show in the example. Once created, it would never be changed, simply passed to a preexisting subroutine. Since both the keys and the values will be consecutive, I could also create the hash using a for loop. I was curious what would be the fastest and/or most efficient way to create the hash? Or if there was yet another way it could be done?
Using map would suffice:
my $valueref = { map { $_ => $_ } 1 .. 40 };
Though one might note here that this is actually an array...
my #array = 0 .. 40;
So $valueref->{$n} is actually $array[$n]. I don't know if there is any benefit to using a hash in this case.
I'd probably use map for this:
my $highest_value = 50;
my %foo = map { $_ => $_ } 1 .. $highest_value ;
Remember that order is not guaranteed in a hash.
"Once created, it would never be changed, simply passed to a
preexisting subroutine"
Sounds like a good candidate for the state keyword (or a closure):
use feature 'state';
sub foo {
my ( $param1, $param2, $limit ) = #_;
state $valueref = { map { $_ => $_ } 0 .. $limit };
...
}
This enables one to initialize the data structure and then not have to worry about passing it as an argument later on.
Hash slice? Something like this:
my %hash;
my $count = 10;
#hash{1..$count} = (1..$count);

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.

Array initialization in Perl

How do I initialize an array to 0?
I have tried this.
my #arr = ();
But it always throws me a warning, "Use of uninitialized value". I do not know the size of the array beforehand. I fill it dynamically. I thought the above piece of code was supposed to initialize it to 0.
How do I do this?
If I understand you, perhaps you don't need an array of zeroes; rather, you need a hash. The hash keys will be the values in the other array and the hash values will be the number of times the value exists in the other array:
use strict;
use warnings;
my #other_array = (0,0,0,1,2,2,3,3,3,4);
my %tallies;
$tallies{$_} ++ for #other_array;
print "$_ => $tallies{$_}\n" for sort {$a <=> $b} keys %tallies;
Output:
0 => 3
1 => 1
2 => 2
3 => 3
4 => 1
To answer your specific question more directly, to create an array populated with a bunch of zeroes, you can use the technique in these two examples:
my #zeroes = (0) x 5; # (0,0,0,0,0)
my #zeroes = (0) x #other_array; # A zero for each item in #other_array.
# This works because in scalar context
# an array evaluates to its size.
What do you mean by "initialize an array to zero"? Arrays don't contain "zero" -- they can contain "zero elements", which is the same as "an empty list". Or, you could have an array with one element, where that element is a zero: my #array = (0);
my #array = (); should work just fine -- it allocates a new array called #array, and then assigns it the empty list, (). Note that this is identical to simply saying my #array;, since the initial value of a new array is the empty list anyway.
Are you sure you are getting an error from this line, and not somewhere else in your code? Ensure you have use strict; use warnings; in your module or script, and check the line number of the error you get. (Posting some contextual code here might help, too.)
To produce the output in your comment to your post, this will do it:
use strict;
use warnings;
my #other_array = (0,0,0,1,2,2,3,3,3,4);
my #array;
my %uniqs;
$uniqs{$_}++ for #other_array;
foreach (keys %uniqs) { $array[$_]=$uniqs{$_} }
print "array[$_] = $array[$_]\n" for (0..$#array);
Output:
array[0] = 3
array[1] = 1
array[2] = 2
array[3] = 3
array[4] = 1
This is different than your stated algorithm of producing a parallel array with zero values, but it is a more Perly way of doing it...
If you must have a parallel array that is the same size as your first array with the elements initialized to 0, this statement will dynamically do it: #array=(0) x scalar(#other_array); but really, you don't need to do that.