dmux
dmux

Reputation: 442

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?

Upvotes: 3

Views: 5741

Answers (4)

ikegami
ikegami

Reputation: 385915

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};
   ...
} 

Upvotes: 3

TLP
TLP

Reputation: 67890

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.

Upvotes: 4

mob
mob

Reputation: 118625

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.

Upvotes: 7

Kerrek SB
Kerrek SB

Reputation: 477150

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 

Upvotes: 3

Related Questions