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};
...
}
Related
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
I have a hash of arrays and I would like to sort them by the array size.
Here is my code so far:
use strict;
use warnings;
my %hash_array = (
"array_1" => ["apple", "ball", "cat"],
"array_2" => ["def", "leppard", "rocks", "too"],
"array_3" => ["italian", "pastry", "missing", "cherry", "top"],
);
# Length of array_1
my $array_1_size = #{$hash_array{"array_1"}};
print "Should print three: $array_1_size\n";
# Found this here: https://stackoverflow.com/questions/15722286
# But my result remains unsorted
foreach my $key ( sort { $hash_array{$b} <=> $hash_array{$a}} keys %hash_array ) {
print "key: $key\n";
}
I understand how to get an individual array's size, but I'm not sure how to combine that with a sort function.
I copied the last foreach block from here but that solution does not work for me as my output is not ordered. I had some questions about the last block of code:
I do not see $b and $a referenced earlier. Is this a built in reference that the sort function understands?
The solution appears to have worked for the original poster, but it is not working in my case. Yet our original goals are similar. What am I missing in order to get an ordered output based on array size?
What is the best way to sort the hash of arrays by array size in ascending and descending order?
You were close. As written, you were comparing the array references, which are more or less memory addresses. I modified your code slightly to give you what you're looking for:
foreach my $key ( sort { scalar(#{$hash_array{$b}}) <=> scalar(#{$hash_array{$a}}) } keys %hash_array ) {
print "key: $key\n";
}
The sort function is exactly that - an anonymous sub. You're not limited to a single comparison, you just need to finish with something that returns -1, 0 or 1. And yes, $a and $b are the two values being compared - they can be scalars or references.
You might solve your problem like this:
foreach my $key (sort {
my $length_a = scalar #{$hash_array{$a}};
my $length_b = scalar #{$hash_array{$b}};
$length_b <=> $length_a
} keys %hash_array) {
...
}
I know that could be reduced to a one-liner, but the point is to show how you can have more than a single comparison inside a sort sub.
for my $key (sort{ my ($x,$y)=#hash_array{$a,$b}; #$y <=> #$x } keys %hash_array) {
print "key: $key\n";
}
$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.
Is there a simple way to validate a hash of hash element comparsion ?
I need to validate a Perl hash of hash element $Table{$key1}{$key2}{K1}{Value} compare to all other elements in hash
third key will be k1 to kn and i want comprare those elements and other keys are same
if ($Table{$key1}{$key2}{K1}{Value} eq $Table{$key1}{$key2}{K2}{Value}
eq $Table{$key1}{$key2}{K3}{Value} )
{
#do whatever
}
Something like this may work:
use List::MoreUtils 'all';
my #keys = map "K$_", 1..10;
print "All keys equal"
if all { $Table{$key1}{$key2}{$keys[1]}{Value} eq $Table{$key1}{$key2}{$_}{Value} } #keys;
I would use Data::Dumper to help with a task like this, especially for a more general problem (where the third key is more arbitrary than 'K1'...'Kn'). Use Data::Dumper to stringify the data structures and then compare the strings.
use Data::Dumper;
# this line is needed to assure that hashes with the same keys output
# those keys in the same order.
$Data::Dumper::Sortkeys = 1;
my $string1= Data::Dumper->Dump($Table{$key1}{$key2}{k1});
for ($n=2; exists($Table{$key1}{$key2}{"k$n"}; $n++) {
my $string_n = Data::Dumper->Dump($Table{$key1}{$key2}{"k$n"});
if ($string1 ne $string_n) {
warn "key 'k$n' is different from 'k1'";
}
}
This can be used for the more general case where $Table{$key1}{$key2}{k7}{value} itself contains a complex data structure. When a difference is detected, though, it doesn't give you much help figuring out where that difference is.
A fairly complex structure. You should be looking into using object oriented programming techniques. That would greatly simplify your programming and the handling of these complex structures.
First of all, let's simplify a bit. When you say:
$Table{$key1}{$key2}{k1}{value}
Do you really mean:
my $value = $Table{$key1}->{$key2}->{k1};
or
my $actual_value = $Table{$key1}->{$key2}->{k1}->{Value};
I'm going to assume the first one. If I'm wrong, let me know, and I'll update my answer.
Let's simplify:
my %hash = %{$Table{$key1}->{$key2}};
Now, we're just dealing with a hash. There are two techniques you can use:
Sort the keys of this hash by value, then if two keys have the same value, they will be next to each other in the sorted list, making it easy to detect duplicates. The advantage is that all the duplicate keys would be printed together. The disadvantage is that this is a sort which takes time and resources.
Reverse the hash, so it's keyed by value and the value of that key is the key. If a key already exists, we know the other key has a duplicate value. This is faster than the first technique because no sorting is involved. However, duplicates will be detected, but not printed together.
Here's the first technique:
my %hash = %{$Table{$key1}->{$key2}};
my $previous_value;
my $previous_key;
foreach my $key (sort {$hash{$a} cmp $hash{$b}} keys %hash) {
if (defined $previous_key and $previous_value eq $hash{$key}) {
print "\$hash{$key} is a duplicate of \$hash{$previous_key}\n";
}
$previous_value = $hash{$key};
$previous_key = $key;
}
And the second:
my %hash = %{$Table{$key1}->{$key2}};
my %reverse_hash;
foreach $key (keys %hash) {
my $value = $hash{$key};
if (exists $reverse_hash{$value}) {
print "\$hash{$reverse_hash{$value}} has the same value as \$hash{$key}\n";
}
else {
$reverse_hash{$value} = $key;
}
}
Alternative approach to the problem is make utility function which will compare all keys if has same value returned from some function for all keys:
sub AllSame (&\%) {
my ($c, $h) = #_;
my #k = keys %$h;
my $ref;
$ref = $c->() for $h->{shift #k};
$ref ne $c->() and return for #$h{#k};
return 1
}
print "OK\n" if AllSame {$_->{Value}} %{$Table{$key1}{$key2}};
But if you start thinking in this way you can found this approach much more generic (recommended way):
sub AllSame (#) {
my $ref = shift;
$ref ne $_ and return for #_;
return 1
}
print "OK\n" if AllSame map {$_->{Value}} values %{$Table{$key1}{$key2}};
If mapping operation is expensive you can make lazy counterpart of same:
sub AllSameMap (&#) {
my $c = shift;
my $ref;
$ref = $c->() for shift;
$ref ne $c->() and return for #_;
return 1
}
print "OK\n" if AllSameMap {$_->{Value}} values %{$Table{$key1}{$key2}};
If you want only some subset of keys you can use hash slice syntax e.g.:
print "OK\n" if AllSame map {$_->{Value}} #{$Table{$key1}{$key2}}{map "K$_", 1..10};
Say I had something like:
# %superhash is some predefined hash with more than 0 keys;
%hash = ();
foreach my $key (keys %superhash){
$superhash{ $key } = %hash;
%hash = ();
}
Would all the keys of superhash point to the same empty hash accessed by %hash or would they be different empty hashes?
If not, how can I make sure they point to empty hashes?
You need to use the \ operator to take a reference to a plural data type (array or hash) before you can store it into a single slot of either. But in the example code given, if referenced, each would be the same hash.
The way to initialize your data structure is:
foreach my $key (keys %superhash) {
$superhash{ $key } = {}; # New empty hash reference
}
But initialization like this is largely unnecessary in Perl due to autovivification (creating appropriate container objects when a variable is used as a container).
my %hash;
$hash{a}{b} = 1;
Now %hash has one key, 'a', which has a value of an anonymous hashref, containing the key/value pair b => 1. Arrays autovivify in the same manner.