Perl: How to get keys on key "keys on reference is experimental" - perl

I am looking for a solution to Perl's warning
"keys on reference is experimental at"
I get this from code like this:
foreach my $f (keys($normal{$nuc}{$e})) {#x, y, and z
I found something similar on StackOverflow here:
Perl throws "keys on reference is experimental"
but I don't see how I can apply it in my situation.
How can I get the keys to multiple keyed hashes without throwing this error?

keys %{$normal{$nuc}{$e}}
E.g. dereference it first.
If you had a reference to start off with, you don't need {} E.g.:
my $ref = $normal{$nuc}{$e};
print keys %$ref;

The problem is that $normal{$nuc}{$e} is a hash reference, and keys will officially only accept a hash. The solution is simple—you must dereference the reference—and you can get around this by writing
for my $f ( keys %{ $normal{$nuc}{$e} } ) { ... }
but it may be wiser to extract the hash reference into an intermediate variable. This will make your code much clearer, like so
my $hr = $normal{$nuc}{$e};
for my $f ( keys %$hr ) { ... }
I would encourage you to write more meaningful variable names. $f and $e may tell you a lot while you're writing it, but it is sure to cause others problems, and it may even come back to hit you a year or two down the line
Likewise, I am sure that there is a better identifier than $hr, but I don't know the meaning of the various levels of your data structure so I couldn't do any better. Please call it something that's relevant to the data that it points to

keys $hash_ref
is the reason for the warning keys on references is experimental
Solution:
keys %{ $hash_ref }
Reference:
https://www.perl.com/pub/2005/07/14/bestpractices.html/

Related

Fixing keys that spring into existence when calling exist on a nested hash

Dear Stackoverflowers,
When calling exist on a hash for testing the existence of keys much further nested in a hash that does not exist, it will create keys leading to final test to see if the final key exist.
The example from perldoc exists is such:
undef $ref;
if (exists $ref->{"Some key"}) {}
print $ref; # prints HASH(0x80d3d5c)
I absolutely love the autovification feature of perl; however, I am now absolutely frightened of using exists for any future projects I may have.
Does anyone know if and/or how one may edit the library perl uses exists from or perhaps use a module to correct for this? It is really silly that if it does not exist, it makes one to see if future keys will exist.
Lastly, learning from the question below in Checking for existence of hash key creates key, one of the comments states OO-style for deeply nested hashes are suggested. Will there be any technical issues with deeply nested (>n=10) and high memory (>8GB) with very simple floating values stored in these nested hashes? Or just issues like this one?
Dereferencing undefined variables[1] is what causes autovivification[2]. Examples of dereferencing:
$ref->{key} and ${$ref}{key}
$ref->[0] and ${$ref}[0]
$$ref
#$ref
etc.
You can avoid it by replacing
exists( $ref->{"Some key"} )
with
$ref && exists( $ref->{"Some key"} )
or by adding
no autovivification;
Under certain circumstances.
"Autovivification" can also be used to refer to the creation of non-existent variables ($x = 1;), elements of hashes (my %h; $h{$key} = 1;) and elements of arrays (my #a; $a[3] = 1;). This post does not address these as it is not relevant here.
Try: perldoc -q multilevel
Or http://perldoc.perl.org/perlfaq4.html#How-can-I-check-if-a-key-exists-in-a-multilevel-hash%3F
You should add
no autovivification;
to the top of your module. This will prevent autovivification from creating the structure down to your test point.

Deferencing hash of hashes in Perl

Sorry for this long post, the code should be easy to understand for veterans of Perl. I'm new to Perl and I'm trying to figure out this bit of code:
my %regression;
print "Reading regression dir: $opt_dir\n";
foreach my $f ( glob("$opt_dir/*.regress") ) {
my $name = ( fileparse( $f, '\.regress' ) )[0];
$regression{$name}{file} = $f;
say "file $regression{$name}{file}";
say "regression name $regression{$name}";
say "regression name ${regression}{$name}";
&read_regress_file( $f, $regression{$name} );
}
sub read_regress_file {
say "args #_";
my $file = shift;
my $href = shift;
say "href $href";
open FILE, $file or die "Cannot open $file: $!\n";
while ( <FILE> ) {
next if /^\s*\#/ or /^\s*$/;
chomp;
my #tokens = split "=";
my $key = shift #tokens;
$$href{$key} = join( "=", #tokens );
}
close FILE;
}
The say lines are things I added to debug.
My confusion is the last part of the subroutine read_regress_file. It looks like href is a reference from the line my $href = shift;. However, I'm trying to figure out how the hash that was passed got referenced in the first place.
%regression is a hash with keys of $name. The .regress files the code reads are simple files contains variables and their values in the form of:
var1=value
var2=value
...
So it looks like the line
my $name = (fileparse($f,'\.regress'))[0];
is creating the keys as scalars and the line
$regression{$name}{file} = $f;
actually makes $name into a hash.
In my debugging lines
say "regression name $regression{$name}";
prints the reference, for instance
regression name HASH(0x7cd198)
but
say "regression name ${regression}{$name}";
prints a name, like
regression name {filename}
with the file name inside the braces.
However, using
say "regression name $$regression{$name}";
prints nothing.
From my understanding, it looks like regression is an actual hash, but the references are the nested hashes, name.
Why does my deference test line using braces work, but the other form of dereferencing ($$) not work?
Also, why is the name still surrounded by braces when it prints? Shouldn't I be dereferencing $name instead?
I'm sorry if this is difficult to read. I'm confused which hash is actually referenced, and how to deference them if the reference is the nested hash.
This is a tough one. You've found some very awkward code that displays what may well be a bug in Perl, and you're getting confused over dereferencing Perl data structures. Standard Perl installations include the full set of documentation, and I suggest you take a look at perldoc perlreftut which is also available online at perldoc.com
The most obvious thing is that you are writing very old-fashioned Perl. Using an ampersand & to call a Perl subroutine hasn't been considered good practice since v5.8 was released fourteen years ago
I don't think there's much need to go beyond your clearly experimentatal lines at the start of the first for loop. Once you have understood this the rest should follow
say "file $regression{$name}{file}";
say "regression name $regression{$name}";
say "regression name ${regression}{$name}";
First of all, expanding data structure references within a string is unreliable. Perl tries to do what you mean, but it's very easy to write something ambiguous without realising it. It is often much better to use printf so that you can specify the embedded value separately. For instance
printf "file %s\n", $regression{$name}{file};
That said, you have a problem. $regression{$name} accesses the element of hash %regression whose key is equal to $name. That value is a reference to another hash, so the line
say "regression name $regression{$name}";
prints something like
regression name HASH(0x29348b0)
which you really don't want to see
Your first try $regression{$name}{file} accesses the element of the secondary hash that has the key file. That works fine
But ${regression}{$name} should be the same as $regression{$name}. Outside a string it is, but inside it's like ${regression} and {$name} are treated separately
There are really too many issues here for me to start guessing where you're stuck, especially without being able to talk about specifics. But it may help if I rewrite the initial code like this
my %regression;
print "Reading regression dir: $opt_dir\n";
foreach my $f ( glob("$opt_dir/*.pl") ) {
my ($name, $path, $suffix) = fileparse($f, '\.regress');
$regression{$name}{file} = $f;
my $file_details = $regression{$name};
say "file $file_details->{file}";
read_regress_file($f, $file_details);
}
I've copied the hash reference to $file_details and passed it to the subroutine like that. Can you see that each element of %regression is keyed by the name of the file, and that each value is a reference to another hash that contains the values filled in by read_regress_file?
I hope this helps. This isn't really a forum for teaching language basics so I don't think I can do much better
What I understand is that this:
$regression{$name}
represents a hashref, which looks like this:
{ file => '...something...'}
So, in order to dereference the hashref returned by $regression{$name}, you have to do something like:
%{ $regression{$name} }
In order to get the full hash.
In order to get the file property of the hash, do this:
$regression{$name}->{file}
Hope this helps.

Perl: reference to a hash to pass to another routine

In code that uploads excel spreadsheets it gives me the data in array ref:
for( #{$listref} ){...
I access it with $_->{'whateverthehashkeyis'} and have no problem.
What I need to do is pass the hash I am accessing in the current iteration of the loop to another subroutine.
This is where I am having problems. I have tried different things with no luck.
This DOES NOT work, but it should be an example of what I need to do
%args = #{$_};
$results = &format_trading_card_preview_item(\%args);
....
sub format_trading_card_preview_item
{
my %args = shift;
I think what I need to do is dereference the hash to send it over. Is that right?
Thanks in advance for any help
It looks like $listref is a reference to an array of hash references.
If you need to use the variable holding the hash references then it is better if you name that variable instead of using the default scalar $_
There is also no point in dereferencing the hash and copying it to %args, only to take a reference to that hash and pass it as a parameter to your subroutine
And it is wrong to call a subroutine with an ampersand & character, and has been so ever since Perl v5.5 landed over seventeen years ago
Your loop should look like this
for my $item ( #$listref ) {
format_trading_card_preview_item($item);
}
Within the subroutine, it depends a lot on what you want to do with the hash passed in, but you don't say anything about that, so it's probably best to leave it as a reference and write
sub format_trading_card_preview_item {
my ($item) = #_;
...
}
or you could use the statement modifier form of for, like this
format_trading_card_preview_item($_) for #$listref;
To answer your question, you don't need to dereference the hash reference in order to pass it to another subroutine. Creating a shallow copy and then taking a reference to that new hash is inefficient, but it would technically work just fine.
However, your problem is that you're confusing hashes and arrays by using the syntax to dereference an array reference on something that is actually a hash reference. In fact, you should have gotten an error message basically saying the same thing:
Not an ARRAY reference at foo.pl line ...
What you actually want to do is something like this:
for my $href (#$listref) { # variable names could be better
# do something
my $results = format_trading_card_preview_item($href);
# do something else
}
sub format_trading_card_preview_item {
my $args = shift;
print $args->{foo};
return 42;
}
Check out perlreftut and perlref for more information on Perl references and nested data structures.

How do I print a hash name in Perl?

Kind of a simple question, but it has me stumped and google just lead me astray. All I want to do is print out the name of a hash. For example:
&my_sub(\%hash_named_bill);
&my_sub(\%hash_named_frank);
sub my_sub{
my $passed_in_hash = shift;
# do great stuff with the hash here
print "You just did great stuff with: ". (insert hash name here);
}
The part I don't know is how to get the stuff in the parenthesis(insert...). For a nested hash you can just use the "keys" tag to get the hash names (if you want to call them that). I can't figure out how to get the entire hash name though, it seems like it really is just another key.
As #hackattack said in the comment, the technical answer to your questions can be found in an answer to Get variable name as string in Perl
However, you should consider whether you are doing the right thing?
If you somehow need to know the name of the hash, you most likely would solve the problem better if you stash those multiple hashes into a hash-of-hashes with names being the keys (which you should be familiar with as you alluded to the approach in your question).
$hash_named_bill{name} = "bill";
$hash_named_frank{name} = "frank";
&my_sub(\%hash_named_bill);
&my_sub(\%hash_named_frank);
sub my_sub{
my $passed_in_hash = shift;
# do great stuff with the hash here
print "You just did great stuff with: ". $passed_in_hash->{name};
}
You can use a name to refer to a hash, but hashes themselves don't have names. For example, consider the following:
*foo = {};
*bar = \%foo;
$foo{x} = 3;
$bar{y} = 4;
Keeping in mind the hash contains (x=>3, y=>4): Is the hash nameless? named 'foo'? named 'bar'? All of the above? None of the above?
The best you can do is approximate an answer using PadWalker. I recommend against using it or similar (i.e. anything that finds a name) in production!
A hash is just a piece of memory, to which a name (or more than one) can be associated.
If you want to print the name of the variable, that's not very straightforward (see haccattack comment), and doesn't smell very well (are you sure you really need that?)
You can also (if this fits your scenario) consider "soft (or symbolic) references" :
%hash1 = ( x => 101, y => 501);
%hash2 = ( x => 102, y => 502);
my_sub("hash1");
#my_sub(\%hash1); # won't work
my_sub("hash2");
sub my_sub {
my $hashname = shift;
print "hash name: $hashname\n";
print $hashname->{x} . "\n";
}
Here you are passing to the function the name of the variable, instead of a (hard) reference to it. Notice that, in Perl, this feels equivalent at the time of dereferencing it (try uncommenting my_sub(\%hash1);), though it's quite a different thing.

Deleting key/value pairs from perl result in Global symbol requires explicit package name error

I am trying to delete certain key/value pairs from a hash, but I get the Global symbol requires explicit package name exception and I don't know how to debug this. I read up on some solutions, but none of them seem to work. So the hash is declared in this fashion:
my $hash = foo();
then I go through the hash using this line of code:
while (my ($key, $value) = each %$hash)
and in the block I select values I don't want and store the keys for these values in an array that was declared like this (before the loop of course):
my #keysArray = ();
I then access the array to retrieve the keys using this code so I can delete them from the hash:
for my $key (#keysArray){
delete $hash{$key};# this line of code is causing the problem
}
The last line that I wrote is the one causing the Global symbol "%hash" requires explicit package name exception.
Any fixes or am I doing something wrong here.
P.S. I changed the variable names and removed other internal code, but the format is the same.
Help please!
Thanks.
delete $hash{$key} deletes an entry from %hash. There is no %hash. Instead you want to write delete $hash->{$key}, which deletes an entry from %$hash.
I suggest perldoc perlreftut for answering all of your questions about references and how to use them.
You've declared $hash (a scalar reference to a hash) but not %hash (a hash). Try doing delete $hash->{$key} instead.
Your (repaired) code:
for my $key (#keysArray) {
delete $hash->{$key};
}
can be shortened to
for my $key (#keysArray) {
delete $$hash{$key};
}
or simply
delete #$hash{#keysArray};