2
votes

How do I sort(and print) a multi-level perl hash based on key value ?

%hash = (
  a => { k1 => 51, k2 => 52, k3 => 53 },
  b => { k1 => 61, k2 => 62, k3 => 63 },
  c => { k1 => 71, k2 => 72, k3 => 73 },
)

For example sort the above hash numerically based on the value of k2? So it should print:

52,62,72

I wanted to know how I can expand sorting single level hashes to multilevel using

sort { $hash{$b} <=> $hash{$a} } keys %hash`

Edit

If I have another hash

my %property = ( a => 7, b => 6, c => 5 )

Can I sort %hash based on numerical value of $hash{key}{k2} * $property{key} using

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

my %hash = (
  a => { k1 => 51, k2 => 52, k3 => 53 },
  b => { k1 => 61, k2 => 62, k3 => 63 },
  c => { k1 => 71, k2 => 72, k3 => 73 },
);

my %property = ( a => 7, b => 6, c => 5 );


foreach (sort { ($hash{$a}{'k2'}*$property{$a}) <=> 
                ($hash{$b}{'k2'}*$property{$b}) } keys %hash)
{
    printf("[%d][%d][%d]\n",
    $hash{$_}{'k2'},$property{$_},$hash{$_}{'k2'}*$property{$_});
}

result should be

72,52,62    as products are (360(72*5),364(52*7),372(62*6))
5
Do you want to sort the k2 values (52, 62, 72) alphabetically by the 1st level key (a, b, c), or do you want to sort all k2 values numerically? - amon
Added an extra question - Jean
@Jean That is a completely new (although loosely related) question. As such, ask a new one. - amon

5 Answers

3
votes

Get a list of all values in the hash:

values %hash;

transform a list of hashrefs to the contents of the k2 entry:

map $_->{k2}, @list

oh, skip it if it's undef/doesn't exist:

map $_->{k2} // (), @list

sort a list numerically:

sort { $a <=> $b } @list

connect the dots:

sort { $a <=> $b } map { $_->{k2} // () } values %hash;
3
votes
sort {$hash{$a}{'k2'} <=> $hash{$b}{'k2'}} keys %hash

The spaceship operator numerically compares the left-hand side with the right-hand side. It's most often seen in its simplest case, with

$a <=> $b

but in this case, you want to compare values from a hash and it can do that too.

1
votes
print join ",", sort { $a <=> $b } map { $_->{k2} } values %hash;
1
votes

This program does as you ask. It first list the values of the k2 elements sorted by their value, then the same elements sorted by their product with the corresponding element of the %property hash.

Note that your expected output of 52,72,62 is wrong. The products are, as you say, a => 364, b => 372, c => 360 so the values should be sorted in the order c, a, b or 72, 52, 62

use strict;
use warnings;

my %hash = (
  a => { k1 => 51, k2 => 52, k3 => 53 },
  b => { k1 => 61, k2 => 62, k3 => 63 },
  c => { k1 => 71, k2 => 72, k3 => 73 },
);

my %property = ( a => 7, b => 6, c => 5 );

print join ',', map { $hash{$_}{k2} } sort {
  my ($aa, $bb) = map { $hash{$_}{k2} } $a, $b;
  $aa <=> $bb;
} keys %hash;
print "\n";

print join ',', map { $hash{$_}{k2} } sort {
  my ($aa, $bb) = map { $hash{$_}{k2} * $property{$_} } $a, $b;
  $aa <=> $bb;
} keys %hash;
print "\n";

output

52,62,72
72,52,62
-1
votes

Go with ysth's answer, but this syntax may make things a bit easier to understand:

sort { $hash{$a}->{k2} <=> $hash{$b}->{k2} } keys %hash;

Remember that $a and $b are hash keys assigned randomly by the sort. You have no idea what key is being assigned to $a or what key is being assigned to $b. At one time $a = "k1" and another time $b = "k1". You don't even know how many times the comparison is being done. All you know is that $a and $b are assigned to values of keys in your hash, and your job is to compare those two keys to get the results you want.

This isn't going to work:

sort { $hash{$a}->{k2} * $property{$a} <=> $hash{$b}->{k2} * $property{$b} } keys %hash;

because $a and $b will be assigned the values k1, k2, or k3. You don't have those keys in your %property hash. You'll probably get a bunch of warnings.

What do you do if your sorting algorithm is more complex than a simple one liner? You can specify a subroutine to do your sorting for you.

For example, instead of sorting on k2, you want to sort on the sum of all the values in your hash. That is $hash{a}->{k1} + $hash{a}->{k2} + $hash->{k3}... vs. $hash{c}->{k1} + $hash{c}->{k2}..., and I have no idea what any of the keys are. This subroutine will find all the keys in %{ hash{$a} } and add them up and compares them vs. all the keys in %{ hash{$b} }:

sort sort_function keys %hash;

sub sort_function {

    # Sum of all the values in the hash %{ $hash{$a} }
    my $sum_a = 0;
    for my $hash_key ( keys %$hash{$a} ) {
       $sum_a += $hash{$a}->{$hash_key};
    }
    # Sum of all the values in the hash %{ $hash{$b} }
    my $sum_b = 0;
    for my $hash_key ( keys %$hash{$b} ) {
       $sum_b += $hash{$b}->{$hash_key};
    }
    return $sum_a <=> $sum_b;
}