lener has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks, I have a Hash of Hashes, in which the first level contains the names of nodes and the second level contains values for their 'cores' and 'memory', it looks like this:
$free_space = { "node1" => { "cores" => 12, "mem" => 200 }, "node2" => { "cores" => 12, "mem" => 500 }, ... }
What I'd like to do is print it out while sorting by 'cores' (descendingly) first, 'memory' (descendingly) second and nodenames (ascendingly) last. So sort by cores, and if the number of cores is equal then sort by memory, if both are equal then sort lexicographically by the node names. It's supposed to look like this:
cores memory node ----- ------ ---- 12 400 node456 12 400 node534 12 350 node23 11 500 node12 11 200 node3 10 900 node10
I've tried doing it "manually" by creating a new HoHoA, where the 'cores' values are first-level keys, 'memory' values are second-level keys and the array holds the names of the nodes, then printing it simply by sorting level by level, like this:
my %CoreMemNodes = (); #HoA <free_cores> -> <free_mem> -> (node1,node2 +,..) foreach my $node (keys %free_space) { push @{ $CoreMemNodes{$free_space{$node}{'cores'}}{$free_space{$no +de}{'mem'}} }, $node; } foreach my $free_cores (sort { $b <=> $a } keys %CoreMemNodes) { foreach my $free_mem (sort { $b <=> $a } keys %{ $CoreMemNodes{$fr +ee_cores} }) { foreach my $node (sort @{ $CoreMemNodes{$free_cores}{$free_mem +} }) { print "SORTED:$free_cores $free_mem $node\n"; } } }
Is there a more efficient solution?

Replies are listed 'Best First'.
Re: sort HoH by second level values in specific order
by 1nickt (Canon) on Dec 15, 2017 at 15:45 UTC

    See sort, which demonstrates how to create a custom sort routine:

    use strict; use warnings; use feature 'say'; my $free_space = { node1 => { cores => 12, mem => 200 }, node2 => { cores => 12, mem => 500 }, node3 => { cores => 10, mem => 600 }, node4 => { cores => 11, mem => 500 }, node5 => { cores => 16, mem => 800 }, node6 => { cores => 16, mem => 800 }, }; for ( sort my_sort keys %{ $free_space } ) { say "$free_space->{$_}->{'cores'} $free_space->{$_}->{'mem'} $_"; } sub my_sort { $free_space->{$b}->{'cores'} <=> $free_space->{$a}->{'cores'} || $free_space->{$b}->{'mem'} <=> $free_space->{$a}->{'mem'} || $a cmp $b }
    Output:
    $ perl 1205589.pl 16 800 node5 16 800 node6 12 500 node2 12 200 node1 11 500 node4 10 600 node3

    Hope this helps!


    The way forward always starts with a minimal test.
      Thanks, that's an excellent answer. I wasn't aware that you can define your own complex sort routines.
Re: sort HoH by second level values in specific order
by LanX (Saint) on Dec 15, 2017 at 15:25 UTC
    I see 2 questions

    1. Well the usual structure for a sorted table is a AoH .

    @space = ( { node => 1 , "cores" => 12, "memory" => 200 }, { node => 2 , "cores" => 12, "memory" => 500 }, ... )

    2. The sort manual describes how to sort for 2 values with an or- combination

    my @new = sort { ($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0] || fc($a) cmp fc($b) } @old;

    Maybe try to combine this and show us what you tried. :)

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Wikisyntax for the Monastery

      That's also a great answer. I've tried that first and it worked flawlessly.
Re: sort HoH by second level values in specific order
by poj (Abbot) on Dec 15, 2017 at 15:50 UTC

    Create an array in the output format you want and sort that.

    #!/usr/bin/perl use strict; my $free_space = { node3 => { cores => 11, mem => 200 }, node10 => { cores => 10, mem => 900 }, node12 => { cores => 11, mem => 500 }, node23 => { cores => 12, mem => 350 }, node456 => { cores => 12, mem => 400 }, node534 => { cores => 12, mem => 400 }, }; my @CoreMemNodes=(); for (keys %$free_space){ my $hr = $free_space->{$_}; push @CoreMemNodes,[$hr->{'cores'},$hr->{'mem'},$_]; }; my @sorted = sort {$b->[0] <=> $a->[0] || $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] } @CoreMemNodes; print "@$_\n" for @sorted;
    poj
Re: sort HoH by second level values in specific order
by choroba (Cardinal) on Dec 16, 2017 at 10:43 UTC
    See also List::UtilsBy on how to specify the sort routines in a simpler way (without using $a and $b, that is). Unfortunately, you can't easily translate nested sorts to it (or sort1($a, $b) || sort2($a, $b): you need to create a single key for each element and compare it as a string or as a number.
    use List::UtilsBy qw{ sort_by }; say join "\t", @{ $free_space->{$_} }{qw{ cores mem }}, $_ for sort_by { sprintf('%03d', 1e4 - $free_space->{$_}{cores}) . sprintf('%05d', 1e6 - $free_space->{$_}{mem}) . $_ } keys %$free_space;
    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: sort HoH by second level values in specific order
by Laurent_R (Canon) on Dec 15, 2017 at 20:43 UTC
    Hi lener,

    this is basically the same idea as poj (and reusing poj's input data), except that this does not bother to create an explicit temporary array:

    #!/usr/bin/perl use strict; use warnings; use feature "say"; my $free_space = { node3 => { cores => 11, mem => 200 }, node10 => { cores => 10, mem => 900 }, node12 => { cores => 11, mem => 500 }, node23 => { cores => 12, mem => 350 }, node456 => { cores => 12, mem => 400 }, node534 => { cores => 12, mem => 400 }, }; say "@$_" for sort { $b->[0] <=> $a->[0] or $b->[1] <=> $a->[1] or $a- +>[2] cmp $b->[2] } map { [$free_space->{$_}{cores}, $free_space->{$_}{mem}, $_ ]} +keys %$free_space;
    This gives the following output:
    12 400 node456 12 400 node534 12 350 node23 11 500 node12 11 200 node3 10 900 node10
    which I believe is what you want.

    This is the idea of a data pipeline. The code should be read from bottom to top (and right to left). The map in the second line creates an in-memory list of anonymous arrays, which is then sorted and displayed by the first line.

Re: sort HoH by second level values in specific order
by Anonymous Monk on Dec 16, 2017 at 01:02 UTC
    All that you need here is a sort-compare subroutine. The sort verb allows you to pass a subroutine-reference as an argument, and this subroutine when called will "magically" find itself presented with two variables: $a and $b. Only it needs to know what to do with them – only it needs to know if they, in fact, represent multi-level hashes: its only duty is to return a value that is less than, equal to, or greater than zero in each and every case.   I therefore suggest that you define a separate sub for this purpose, and that you should carefully desk-check it in advance against a number of different hand-built test-case HOH values.