Hash in Perl adds key if it does not exist - perl

I have the following perl script which is storing some details in a hash. After populating some entries in the hash, I'm printing the content of the hash which produces the following output
Key:4:Name4 Value:Name4
Key:3:Name3 Value:Name3
Key:2:Name2 Value:Name2
Key:1:Name1 Value:Name1
Key:0:Name0 Value:Name0
After that I am trying the get search for a hey which does not exist in the hash (my $nm = $components{'11:Name11'}{'name'} );
After this check If I print the content of hash, I see that above key (i.e '11:Name11') is getting added to hash (highlighted below). Can someone explain this behavior please?
Key:4:Name4 Value:Name4
Key:3:Name3 Value:Name3
**Key:11:Name11 Value:**
Key:2:Name2 Value:Name2
Key:1:Name1 Value:Name1
Key:0:Name0 Value:Name0
my %components ;
for ($i=0;$i<5;$i++)
{
my $hash = {} ;
my $vr = $i+100;
$hash->{'container'} = $i ;
$hash->{'name'} = 'Name'.$i;
$hash->{'version'} = $vr ;
my $tmpCompName = $hash->{'container'}.':'.$hash->{'name'};
$components{$tmpCompName} = $hash ;
}
while (my ($k,$v)=each %components){
print "Key:".$k." Value:".$v->{'name'}."\n";
}
my $tmp = '11:Name11';
my $nm = $components{$tmp}{'name'} ;
print "Name:".$nm."\n";
print "After check\n";
while (my ($k,$v)=each %components){
print "Key:".$k." Value:".$v->{'name'}."\n"
}
Thanks in advance.

This is called autovivification. It is a feature of Perl that allows you to use a hash element that you haven't previously declared or initialized. It occurs whenever an undefined value (like $components{'11:Name11'}) is dereferenced (which happens when Perl tries to evaluate $components{'11:Name11'}{'name'}).
There is a autovivification pragma that you can unuse to disable this behavior.
{
no autovivification;
if ($hash{"non-existent-key"}{"foo"}) { # won't create $hash{"non-existent-key"}
...
}

Related

Accessing and displaying key-value pairs in Perl from subroutine

New to Perl. Got syntax errors in accessing key-value pairs from subroutine.
sub displayObj{
my %obj = shift; //the hash. It is a JSON object after decode_json()
my $field = shift; //the key we are searching. It is a string.
my $serialized = "{}"; //Initialization
foreach my $key (keys %obj){
if($key eq $field){
$serialized = $obj[{$field}];
}
}
print "$serialized\n";
}
It is giving me a compilation error in the if block.
I would like to know:
Should I use % or $ in my %obj?
How to access the key-value pair (also a JSON object) and assign it to $serialized using $obj?
I think you're trying to write a subroutine that displays the value of a hash element given its key
But you're missing the basic purpose of hashes: they're content-addressable. That means there's no need to iterate through all the keys of a hash comparing them to the string you're looking for, you can write just $hash{key} and the search will be done for you very quickly using a hashing system (hence the name of the data type). This is just the same as using $array[$i] to access an array element directly instead of looping over all of the indices of the array comparing each one to $i until you find the element you're looking for
If you're really dealing with objects then you shouldn't be accessing their internal data like this anyway. An object will have accessor methods that return the values you're supposed to be using; anything else is part of the internal working of the class and is meant to be private
The syntax error is in this line
$serialized = %obj[{$field}]
where it looks like you're trying to use both a hash key {...} and an array index [...]. That won't work
You don't show how you're calling your subroutine, but I imagine you're passing a reference to a hash, which is a scalar value and must be treated as such inside the subroutine
This program shows a working version of what I think you intended
use strict;
use warnings 'all';
my $obj = {
aa => 1,
cc => 2,
};
displayObj($obj, 'cc');
displayObj($obj, 'bb');
sub displayObj {
my $obj = shift; # The hash. It is a JSON object after decode_json()
my $field = shift; # The key we are searching. It is a string.
my $serialized = '{}'; # Initialization
foreach my $key ( keys %$obj ) {
if ( $key eq $field ) {
$serialized = $obj->{$field};
}
}
print "$serialized\n";
}
output
2
{}
But the loop can be removed altogether as I described, leaving
sub displayObj {
my ($obj, $field) = #_;
my $serialized = $obj->{$field} // '{}';
print "$serialized\n";
}
which produces exactly the same result. In fact there's little point in creating a subroutine to do this; you can write just
print $obj->{bb} // '{}', "\n"
with the same effect
I usually do this way:
sub displayObj{
my $objref = shift;
my %obj = %{$objref};
}

Not able to print value of this complex datastructure in perl

Question explained as comments in the code:
I have a following piece of code in which i am trying to make a hash in which the key itself is a reference to some other array.
my #arr1=(1,2,3,4,5,6,7,8,9,10);
my #arr2=(1001,1002,1003);
$FILES_1=\#arr1;
$num1=2;
$FILES_2=\#arr2;
$num2=4;
#FILES=($FILES_1, $FILES_2);
#NUMS=($num1,$num2);
fun (\#FILES,\#NUMS);
sub fun{
my ($rFILES,$rNUMS) = #_;
print "\n --${$rFILES}[0]->[2] \n "; # This is same as below
print "\n --$rFILES->[0]->[2] \n "; # This is same as above
my %hash=();
$hash{$rFILES->[0]} = $rNUMS->[0];
my $test = $rFILES->[0];
print "\nTEST : $test->[1]";
my #key = keys %hash;
print "\nKey 1 = $key[0]"; # This prints scalar value as below
print "\ntest = $test "; # This prints scalar value as above
print "\nKey 1->[1] = ${$key[0]}->[1]"; #PROBLEM : THIS DOESNT PRINT SAME AS BELOW
print "\ntest->[1] = $test->[1] "; #THIS PRINTS AS EXPECTED.
}
Output:
--3
--3
TEST : 2
Key 1 = ARRAY(0x1bbb540)
test = ARRAY(0x1bbb540)
Key 1->[1] =
test->[1] = 2
Are we not supposed to keep a key of a hash as reference to some array? Why is the value "2" not printed?
A hash key is always a string, you can't store a reference in there. I can't think of a reason you'd want this either.
You should really always use strict and use warnings. If you added those and declared your variables with my properly, you'd have gotten a warning:
Can't use string ("ARRAY(0x85e628)") as a SCALAR ref while "strict refs" in use
The syntax you're using:
${$key[0]}
says $key[0] is a reference to a scalar. It isn't, it's a string with the address of what the reference used to be, as you can't use a reference as a hash key they become strings.
Update:
You probably want something like this instead:
my #filegroups = (
{ number => 1, files => ['file1','file2'] },
{ number => 2, files => ['file3'] },
);
Accessed as:
foreach my $filegroup ( #$filegroups ) {
print "my number is $filegroup->{number}\n";
print " and I have ".scalar( #{ $filegroup->{ files } } )." files\n";
}
If you need extra speed to access the structure by group number (don't bother unless you have hundreds of groups or thousands and thousands of accesses), index them:
my %filegroupindexes = map { $_->{ number } => $_ } values #$filegroups;
Then you can get to the group like this:
print "there are ".scalar( #{ $filegroupindexes{ 1 }->{ files } } )." files in group 1\n";
As a last hint, for printing complex data structures Data::Printer is my favourite:
use Data::Printer colored => 1;
p( #filegroups );
You need Tie::RefHash if you want to use references as hash keys.

Trouble passing hash and variable to subroutine

I want to pass a hash and a variable to a subroutine:
%HoA = {'1'=>'2'};
my $group_size = 10;
&delete_unwanted(\%HoA,$group_size);
sub delete_unwanted {
my (%HoA,$group_size) = #_;
print "'$group_size'\n"
}
But, this prints nothing.
You're passing a hash reference (as you should), so therefore assign it to a scalar in your parameter catching:
sub delete_unwanted {
my ($hashref, $group_size) = #_;
print "'$group_size'\n"
}
If you later want to dereference it, you can my %newHoA = %$hashref;, but that will be a copy of the original hash. To access the original structure, just use the reference: print $hashref->{a_key};.
Your problem is in:
my (%HoA,$group_size) = #_;
You can solve it by saying, for example:
sub delete_unwanted {
my $hashPointer = shift;
my $group_size = shift
Note that you can retrieve the original hash inside the subroutine by either: de-referencing the hashPointer (my %HoA = %$hashPointer), or you can access the hash contents directly using the pointer directly (eg, $hashPointer->{'key'})

Reference found where even-sized list expected

I am writing this code in perl where i create a unique key and then assign a value to it.
sub populate {
my $file = shift;
my %HoH = shift;
open(INFILE,$file);
.
.
.
$final_name = $prepend.$five;
$HoH{$final_name} = $seven;
}
Now i am passing in two parameters to a subroutine which id like
&populate(\%abc,$file_1);
&populate(\%xyz,$file_2);
Why does it give me an error like this:
Reference found where even-sized list expected
Because your hash is assigned to a reference, and not a hash (even-sized list). You need to do:
my $hashref = shift;
...
$hashref->{$final_name} = $seven;
ETA: You should call subroutines without &, e.g. populate(...), unless you specifically want to override the prototype of the sub. If you don't know what a prototype is, just don't use &.
ETA2: You really should use a lexical filehandle and three-argument open. Consider this scenario:
open INFILE, $file;
some_sub();
$args = <INFILE>; # <--- Now reading from a closed filehandle
sub some_sub {
open INFILE, $some_file;
random code...
close INFILE;
}

Is this the correct way to build a Perl hash that utilizes arrays?

This is the first time I have manipulated hashes and arrays in this way -- and it is working. Basically, for every key there are multiple values that I want to record and then print out in the form "key --> value --> value --> val..."
My code is as follows. I am surprised that it works, so concerned that it works "by mistake". Is this the correct way to accomplish this task, or is there a more efficient or appropriate method?
while ($source =~ m/(regex)/g) { #Get all key names from source
$listkey = $1; #Set current list key to the current regex result.
$list{$listkey} = ++$i unless $list{$listkey}; #Add the key to the hash unless it already exists.
$list{$listkey} = [] unless exists $list{$listkey}; #Add an array for the hash unless the hash already exists.
while ($loopcount==0) {
if ($ifcount==0) {
$listvalue=result_of_some_function_using_list_key; #Get the first list value by using the list key.
$ifcount++; #Increment so we only get the first list value once.
} else {
$listvalue=result_of_some_function_using_list_value; #Update the list value by using the last list value.
}
if ($listvalue) { #If the function returned a value...
push #{$list{$listkey}}, $listvalue; #...then add the value to the hash array for the key.
} else { #There are no more values and we need a new key.
$listkey=0; #Reset variable.
$listvalue=0; #Reset variable.
$loopcount++; #Increment loop counter to exit loop.
}
}
$ifcount=0; #Reset count variable so the next listvalue can be generated from the new key.
$loopcount=0; #Reset count variable so another loop can begin for a new key.
}
foreach $listkey (keys %list) { #For each key in the hash.
print "$listkey --> "; #Print the key.
#values = #{$list{$listkey}}; #Reference the arrays of the hash.
print join ' --> ', #values; #Print the values.
print "\n"; #Print new line.
}
The following code does the same as your code, without the unnecessary steps.
while ($source =~ m/(regex)/g) { # Get all key names from source
$listkey = $1; # Grab current regex result.
$listvalue = result_of_some_function_using_list_key;
while ($listvalue) {
push #{$list{$listkey}}, $listvalue;
$listvalue = result_of_some_function_using_list_value;
}
$listkey = 0; # Reset variable.
$domain = 0; # Reset variable.
}
However, as others have commented, global variables should be avoided in most cases. Instead, the list key and list value should be lexically scoped with my(), and the functions for generating list values should take one or more parameters (domain, list key and/or list value) as input.
The lines
$list{$listkey} = ++$i unless $list{$listkey};
$list{$listkey} = [] unless exists $list{$listkey};
in your original code aren't needed, it is sufficient with push #{ $list{$key} }, $value to initialize an entry.
The code above has many unnecessary steps. Perl is a very expressive language, and allows logic like this to be expressed very simply:
# uncomment for some sample data
# sub function {"#_" !~ /^\[{3}/ and "[#_]"}
# my $source = 'one two three';
my %list;
while ($source =~ m/(\S+)/g) {
my $key = $1;
my $value = function($key);
while ($value) {
push #{ $list{$key} }, $value;
$value = function($value)
}
}
for my $key (keys %list) {
print join(' --> ' => $key, #{$list{$key}}), "\n";
}
Nope! If this works, it's definitely "by mistake". But it's also obvious that this isn't your real code and that you added several more mistakes in "translating" it to an example, so it's hard to judge exactly what the intent was, but going from the skeleton of your program, it looks like it should be something like:
my %result;
while ($source =~ m/(regex)/g) {
my $key = $1;
my $value = mangle($key);
while ($value) {
push #{ $results{$key} }, $value;
$value = frob($value);
}
}
and no more. Your attempts to initialize the hash aren't doing what you think they are (and aren't necessary), your while loop as written isn't a good idea at all, and neither are all the global variables.