How to "keys %h" if $h is an object? - perl

$h below is an object, but it only contains a regular hash.
my $h = YAML::Syck::LoadFile('have_seen.yaml');
If it was a normal hash then the number of keys would just be keys $h.
Question
How to get the numbers of keys when the hash is in an object?
Update
This is code
#!/usr/bin/perl
use strict;
use YAML::Syck;
my $h = YAML::Syck::LoadFile('h.yaml');
my $links = 100;
print $links - keys $h . "\n";
The yaml file contains
---
010711: 1
---

$h is not an object, but a plain hashref. This is really an operator precedence problem. Use parentheses to bind the argument to the keys function tight.
print $links - keys($h) . "\n";
As Greg Bacon pointed out, on old Perls it is necessary to manually dereference first with %$h or %{ $h } (which is the better style).

Use the keys operator as in
print scalar keys %$h;
Most of the time, an explicit scalar is unnecessary, e.g.,
my $n = keys %$h;
But it’s usually a bad idea to go poking into the internals of an object. Use the public interface instead. Why do you want to do it this way?

My code was also producing the same error Type of argument to keys on reference must be unblessed hashref or arrayref but the difference is that the hash was produced from my own object.
sub getAttributes {
my $self = shift;
return $self->{ATTRIBUTES};
}
I tried a few ways to get keys to de-reference what is returned by $instance->getAttributes() but it seems that once it has been blessed the keys function doesn't want to know.

Related

Perl: passing hash by ref using rule1

I am still unclear about why by ref portion is showing undefined value for %Q and $_ uninitialized. I have been looking through perlreftut and still unable to see what I have done wrong. Passing the hash as a flat array has no issue.
Doing it by ref with testRef(\%mkPara) passes a scalar hash reference to the subroutine, right? So, does my %Q = %{$_} not turn it back into a hash?
use strict;
use diagnostics;
use warnings;
my %mkPara = ('aa'=>2,'bb'=>3,'cc'=>4,'dd'=>5);
sub testFlat
{
my %P = #_;
print "$P{'aa'}, $P{'bb'}, ", $P{'cc'}*$P{'dd'}, "\n";
}
sub testRef
{
my %Q = %{$_}; #can't use an undefined value as HASH reference
#print $_->{'aa'}, "\n";#Use of uninitialized value
print $Q{'aa'},"\n";
}
#testFlat(%mkPara);
testRef(\%mkPara);
When you use arguments in a function call (\%mkPara in your case), you can access them through #_ array inside the function.
Here, you pass a single argument to the function : \%mkPara, which you can then access by accessing the first element of #_ by using $_[0].
$_ is the default variable for some builtin functions/operators (print, m//, s///, chomp and a lot more). Usually seen in while or for loops. But in your code, you have no reason to use it (you are never setting it to anything, so it's still set to undef, hence the error "Can't use an undefined value as a HASH reference".
So your function should actually be :
sub testRef
{
my %Q = %{$_[0]}; # instead of %{$_}
print $_[0]->{'aa'}, "\n"; # instead of $_->{'aa'}
print $Q{'aa'},"\n";
}
If needed, you can find more about functions on perlsub.
However, as #Ikegami pointed out in the comments, using my %Q = %{$_[0]}; creates a copy of the hash you sent to the function, which in most cases (including that one where you just print a key of the hash) is very suboptimal as you could just use a hashref (like you are doing when you do $_[0]->{'aa'}).
You can use hash references like this (roughly the same example as the answer of #Zaid) :
sub testRef
{
my ( $Q ) = #_;
print $Q->{aa} ;
print $_, "\n" for keys %$Q;
}
testRef(\%mkPara);
There are quite a lot of resources about references online, for instance perlreftut that you were already looking at.
This can seem a bit tricky at first, but the reason is that $_ is not the same as #_.
From perlvar:
$_ is the implicit/"default" variable that does not have to be spelled out explicitly for certain functions (e.g. split )
Within a subroutine the array #_ contains the parameters passed to that subroutine
So the reason why
my %Q = %{$_};
says you can't use an undefined value as hash reference is because $_ is not defined.
What you really need here is
my %Q = %{$_[0]};
because that is the first element of #_, which is what was passed to testRef in the first place.
In practice I tend to find myself doing things a little differently because it lends itself to flexibility for future modifications:
sub testRef {
my ( $Q ) = #_;
print $_, "\n" for keys %$Q; # just as an example
}

Perl array of hashes, keys return an error

I have this code where array is an array of hashes:
my $hash = $array[0];
print "REF: " . ref($hash) . "\n";
my #names = keys ($hash);
The REF prints HASH so I know it is a hash.
But then the keys function returns an error:
Type of arg 1 to keys must be hash
How can I use the $hash as a hash?
Thanks!
$hash isn't a hash, it's a hash reference. Therefore you need to dereference it before you can run keys on it.
Simplest way of doing this:
keys %$hash;
e.g.
foreach my $key ( keys %$hash ) {
print $key, " => ", $hash -> {$key},"\n";
}
And yes, I am mixing two dereference methods deliberately. The -> notation says 'dereference this' - it's commonly used for object oriented stuff.
For more complex dereferencing %$hash{'key'} is ambiguous, so you start needing brackets - %{$hash{'key'}} for example.
See:
http://perldoc.perl.org/perlreftut.html
http://perldoc.perl.org/perlref.html

How to use a 'subroutine reference' as a hash key

In Perl, I'm learning how to dereference 'subroutine references'. But I can't seem to use a subroutine reference as a hash 'key'.
In the following sample code,
I can create a reference to a subroutine ($subref) and then dereference it to run the subroutine (&$subref)
I can use the reference as a hash 'value' and then easily dereference that
But I cannot figure out how to use the reference as a hash 'key'. When I pull the key out of the hash, Perl interprets the key as a string value (not a reference) - which I now understand (thanks to this site!). So I've tried Hash::MultiKey, but that seems to turn it into an array reference. I want to treat it as a subroutine/code reference, assuming this is somehow possible?
Any other ideas?
use strict;
#use diagnostics;
use Hash::MultiKey;
my $subref = \&hello;
#1:
&$subref('bob','sue'); #okay
#2:
my %hash;
$hash{'sayhi'}=$subref;
&{$hash{'sayhi'}}('bob','sue'); #okay
#3:
my %hash2;
tie %hash2, 'Hash::MultiKey';
$hash2{$subref}=1;
foreach my $key (keys %hash2) {
print "Ref type is: ". ref($key)."\n";
&{$key}('bob','sue'); # Not okay
}
sub hello {
my $name=shift;
my $name2=shift;
print "hello $name and $name2\n";
}
This is what is returned:
hello bob and sue
hello bob and sue
Ref type is: ARRAY
Not a CODE reference at d:\temp\test.pl line 21.
That is correct, a normal hash key is only a string. Things that are not strings get coerced to their string representation.
my $coderef = sub { my ($name, $name2) = #_; say "hello $name and $name2"; };
my %hash2 = ( $coderef => 1, );
print keys %hash2; # 'CODE(0x8d2280)'
Tieing is the usual means to modify that behaviour, but Hash::MultiKey does not help you, it has a different purpose: as the name says, you may have multiple keys, but again only simple strings:
use Hash::MultiKey qw();
tie my %hash2, 'Hash::MultiKey';
$hash2{ [$coderef] } = 1;
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key);
say 'Ref of the list elements produced by array-dereferencing the key are:';
say ref($_) for #{ $key }; # no output, i.e. simple strings
say 'List elements produced by array-dereferencing the key are:';
say $_ for #{ $key }; # 'CODE(0x8d27f0)'
}
Instead, use Tie::RefHash. (Code critique: prefer this syntax with the -> arrow for dereferencing a coderef.)
use 5.010;
use strict;
use warnings FATAL => 'all';
use Tie::RefHash qw();
my $coderef = sub {
my ($name, $name2) = #_;
say "hello $name and $name2";
};
$coderef->(qw(bob sue));
my %hash = (sayhi => $coderef);
$hash{sayhi}->(qw(bob sue));
tie my %hash2, 'Tie::RefHash';
%hash2 = ($coderef => 1);
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key); # 'CODE'
$key->(qw(bob sue));
}
From perlfaq4:
How can I use a reference as a hash key?
(contributed by brian d foy and Ben Morrow)
Hash keys are strings, so you can't really use a reference as the key.
When you try to do that, perl turns the reference into its stringified
form (for instance, HASH(0xDEADBEEF) ). From there you can't get back
the reference from the stringified form, at least without doing some
extra work on your own.
Remember that the entry in the hash will still be there even if the
referenced variable goes out of scope, and that it is entirely
possible for Perl to subsequently allocate a different variable at the
same address. This will mean a new variable might accidentally be
associated with the value for an old.
If you have Perl 5.10 or later, and you just want to store a value
against the reference for lookup later, you can use the core
Hash::Util::Fieldhash module. This will also handle renaming the keys
if you use multiple threads (which causes all variables to be
reallocated at new addresses, changing their stringification), and
garbage-collecting the entries when the referenced variable goes out
of scope.
If you actually need to be able to get a real reference back from each
hash entry, you can use the Tie::RefHash module, which does the
required work for you.
So it looks like Tie::RefHash will do what you want. But to be honest, I don't think that what you want to do is a particularly good idea.
Why do you need it? If you e.g. need to store parameters to the functions in the hash, you can use HoH:
my %hash;
$hash{$subref} = { sub => $subref,
arg => [qw/bob sue/],
};
foreach my $key (keys %hash) {
print "$key: ref type is: " . ref($key) . "\n";
$hash{$key}{sub}->( #{ $hash{$key}{arg} } );
}
But then, you can probably choose a better key anyway.

Perl : Dereference between #_ and $_

I have a question with the following Code:
#!/usr/bin/perl
use strict;
use warnings;
my %dmax=("dad" => "aaa","asd" => "bbb");
my %dmin=("dad" => "ccc","asd" => "ddd");
&foreach_schleife(\%dmax,\%dmin);
sub foreach_schleife {
my $concat;
my $i=0;
foreach my $keys (sort keys %{$_[0]}) {
while ($_[$i]) {
$concat.="$_[$i]{$keys} ";
print $_[$i]{$keys}."\n";
$i++;
}
$i=0;
$concat="";
}
}
The Output is:
bbb
ddd
aaa
ccc
I don't understand this.
Normally you must dereference references on hashes,arrays etc.
Why not here? Its enough to write :
$_[$i]{$keys}."\n";
and not something like that:
$$_[$i]{$keys}."\n";
Why?
Has it something to do with the speciality of the variable #_/$_?
My guess is that because an array (or a hash, for that matter) can only contain hash references your second act of indexing means that the reference is understood.
I think the developers need to document this a little better.
To see that it is not special to *_, you can try this before the loop:
my #a = #_;
And this during:
print $a[$i]{$keys}."\n";
I think the main thing is that if you only have a scalar reference as the base, then at least one -> is required. So
my ( $damxr, $dminr ) = #_;
would require
$dmaxr->{ $key };
The reason why you don't have to dereference $_[0] and $_[1] is that $_[$i]{$keys} is a valid short-hand notation for $_[$i]->{$keys} when your reference is in an array or a hash.
$$_[$i]{$keys} won't work, because it will try to dereference the special variable $_ to a scalar. The correct syntax is %{$_[$i]}, but then you'll have to use %{$_[$i]}->{$keys}, which is more verbose.
#_ is the array of subroutine arguments, hence $_[$index] accesses the element at $index
Dereferencing is only good if you have references, but #_ isn't one.

Reversing a hash, getting its keys, and sorting

I'm still learning Perl, so there's probably a more efficient way of doing this. I'm trying to take a hash, reverse it so $values => $keys, grab the new key (the old value) and then sort those keys.
Here's the code in question:
foreach my $key (sort keys reverse %hash){
...}
What I'm expecting to happen is that reverse %hash will return a hash type, which is what keys is looking for. However, I get the following error:
Type of arg 1 to keys must be hash (not reverse)
I've tried putting parentheses around reverse %hash, but still get the same thing.
Any ideas why this wouldn't work?
Perl functions can either return scalar values or lists; there is no explicit hash return type
(You can call return %hash from a subroutine, but Perl implicitly unrolls the key-value pairs from the hash and returns them as a list).
Therefore, the return value of reverse %hash is a list, not a hash, and not suitable for use as an argument to keys. You can force Perl to interpret this list as a hash with the %{{}} cast:
foreach my $key (sort keys %{{reverse %hash}}) { ...
You could also sort on the values of the hash by saying
foreach my $key (sort values %hash) { ...
Using values %hash is subtly different from using keys %{{reverse %hash}} in that keys %{{reverse %hash}} will not return any duplicate values.
The argument to keys must be a hash, array or expression, not a list. If you did
keys { reverse %hash }
you would get the result you expect, because the brackets create an anonymous hash. Parens, on the other hand, only change the precedence. Or, in this case they are probably considered related to the function keys(), as most perl functions have optional parens.
Also, if you just want the values of the hash, you can use:
values %hash
See the documentation for reverse, values and keys for more info.
I think you're describing exactly the situation in this example:
#!/usr/local/bin/perl
use strict;
use warnings;
my %hash = (one => 1, two => 2, three => 3, four => 4);
%hash = reverse %hash;
foreach my $key (sort {$a <=> $b} keys %hash) {
print "$key=>$hash{$key}, ";
}
print "\n";
# it displays: 1=>one, 2=>two, 3=>three, 4=>four
Pre 5.14, keys returns the keys of hash. That requires a hash. You didn't provide one. reverse doesn't return a hash. In fact, it's impossible to return a hash as only scalars can be returned. (Internally, Perl can put hashes directly on the stack, but that will never be visible to the user without causing a "Bizarre" error message.) This error is detected at compile-time.
5.14 is more flexible. It will also accept a reference to a hash. (It will also accept arrays and references to arrays, but that's not relevant here.) References are scalars, so they can be returned by functions. Your code will actually make it to run-time, but whatever your reverse returns in scalar context won't a reference to a hash that doesn't exist, so your code will die at that point.
Do you have a reason to want to reverse the hash?
foreach my $key (sort { $hash{$a} cmp $hash{$b} } keys %hash) {
my $val = $hash{$key};
...
}
If you do,
foreach my $val (sort keys %{ { reverse %hash } }) {
# No access to original key
...
}
or
my %flipped = reverse %hash;
foreach my $val (sort keys %flipped) {
my $key = $flipped{$val};
...
}