3

Can anybody tell me what I am doing wrong here? I have tried just about every possible combination of array / hash type and sort query I can think of and cannot seem to get this to work.

I am trying to sort the hash ref below by value1 :

my $test = {
    '1' => { 'value1' => '0.001000', 'value2' => 'red'},
    '2' => { 'value1' => '0.005000', 'value2' => 'blue'},
    '3' => { 'value1' => '0.002000', 'value2' => 'green'},
    '7' => { 'value1' => '0.002243', 'value2' => 'violet'},
    '9' => { 'value1' => '0.001005', 'value2' => 'yellow'},
    '20' => { 'value1' => '0.0010200', 'value2' => 'purple'}
};

Using this sort loop:

foreach (sort { $test{$a}->{'value1'} <=> $test{$b}->{'value1'} } keys \%{$test} ){
    print "key: $_ value: $test->{$_}->{'value1'}\n"
}

I get:

key: 1 value: 0.001000
key: 3 value: 0.002000
key: 7 value: 0.002243
key: 9 value: 0.001005
key: 2 value: 0.005000
key: 20 value: 0.0010200

I have tried with integers and the same thing seems to happen.

I don't actually need to loop through the hash either I just want it ordered for later use. Its easy to do with an array of hashes, but not so with a hash of hashes..?

Farzad
  • 842
  • 2
  • 9
  • 26
someuser
  • 2,279
  • 4
  • 28
  • 39
  • 3
    Re. "I don't actually need to loop through the hash either I just want it ordered for later use." You can't sort a hash. You can iterate through the keys in a certain order, but the hash itself will not be ordered. – ThisSuitIsBlackNot Apr 07 '14 at 15:50
  • Thanks! That was the answer I was ultimately looking for. Back to arrays then... I assume it is faster to sort an array once than sort a hash every time you need to use it which in my current case is many per second (I guess you could create a new hash on iterating through the keys). – someuser Apr 07 '14 at 16:24
  • Again, you can't sort a hash. You can store a sorted list of keys in an array and use that to set the order you use for iterating through the hash, but that will only work if you don't add or remove elements from the hash. `my @sorted = sort keys %hash; for (@sorted) { ... }` – ThisSuitIsBlackNot Apr 07 '14 at 16:35

4 Answers4

6

Don't call keys on a reference. Call it on the actual hash.

Also, this $test{$a}->, should be $test->{$a}, because $test is a hash reference.

foreach (sort { $test->{$a}{'value1'} <=> $test->{$b}{'value1'} } keys %{$test} ){
    print "key: $_ value: $test->{$_}->{'value1'}\n"
}

If you had use strict; and use warnings; turned on, you would've gotten the following error to alert you to an issue:

Global symbol "%test" requires explicit package name
Miller
  • 34,962
  • 4
  • 39
  • 60
3

Just wanted to provide a source for the other answers, and a working code example. Like they said, you are calling keys with a hash reference for the argument. According to the documentation:


Starting with Perl 5.14, keys can take a scalar EXPR, which must contain a reference to an unblessed hash or array. The argument will be dereferenced automatically. This aspect of keys is considered highly experimental. The exact behaviour may change in a future version of Perl.

for (keys $hashref) { ... }
for (keys $obj->get_arrayref) { ... }

However this does work for me:

#!/usr/bin/perl
use strict;
use warnings;

my $test = {
    '1' => { 'value1' => '0.001000', 'value2' => 'red'},
    '2' => { 'value1' => '0.005000', 'value2' => 'blue'},
    '3' => { 'value1' => '0.002000', 'value2' => 'green'},
    '7' => { 'value1' => '0.002243', 'value2' => 'violet'},
    '9' => { 'value1' => '0.001005', 'value2' => 'yellow'},
    '20' => { 'value1' => '0.0010200', 'value2' => 'purple'}
};


foreach (sort { $test->{$a}->{'value1'} <=> $test->{$b}->{'value1'} } keys \%{$test} ) {
    print "key: $_ value: $test->{$_}->{'value1'}\n"
}

Example:

matt@mattpc:~/Documents/test/10$ perl test.pl 
key: 1 value: 0.001000
key: 9 value: 0.001005
key: 20 value: 0.0010200
key: 3 value: 0.002000
key: 7 value: 0.002243
key: 2 value: 0.005000
matt@mattpc:~/Documents/test/10$ perl --version
This is perl 5, version 14, subversion 2 (v5.14.2) built for x86_64-linux-gnu-thread-multi
(with 88 registered patches, see perl -V for more detail)

This is with using a hash reference as the input to keys which I would not recommend.

I'd recommend following the advice of the other questions and adding use strict and use warnings and changing the hash reference to a hash, %{test}.

hmatt1
  • 4,939
  • 3
  • 30
  • 51
2

It's simply keys %$test. The argument of keys must be a hash, not a hashref. \%${test} is the same as $test, a ref.

And use $test->{$a}, not $test{$a}, as $test is a hash-ref, not a hash.

foreach (sort { $test->{$a}->{'value1'} <=> $test->{$b}->'{value1'} } keys %$test) {
    print "key: $_ value: $test->{$_}->{'value1'}\n"
}

or a shorter form with some syntactic sugar: You can omit the additional arrows after the first one. And you don't have to quote string literal keys when addressing hashes.

foreach (sort { $test->{$a}{value1} <=> $test->{$b}{value1} } keys %$test) {
    print "key: $_ value: $test->{$_}{value1}\n"
}

It usually helps a lot to turn on use warnings;, at least for debugging.

SzG
  • 12,333
  • 4
  • 28
  • 41
0

The only wrong thing I can spot is usage of hash ref \%{$test} where you should use hash %$test. keys work with that.

Suor
  • 2,845
  • 1
  • 22
  • 28