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

I have a hash table named @data. I have been using the line
foreach $junk (sort {$b->{'count'} cmp $a->{'count'}} @data) { print "$junk->{'count'} --> $junk->{'type'} --> $junk->{'a_point'} + --> $junk->{'z_point'} --> $junk->{'error'}\n"; }
I would like to be able to continue to sort on count but then after count I would like to sort on type since I have many types and they may have the same count numbers. Any ideas? Thanks in advance!!

Replies are listed 'Best First'.
Re: how do I sort on two fields in a hash table?
by lhoward (Vicar) on Mar 15, 2001 at 22:50 UTC
    foreach $junk (sort {$b->{'count'} cmp $a->{'count'} or $b->{'type'} cmp $a->{'type'}} @data) { print "$junk->{'count'} --> $junk->{'type'} --> $junk->{'a_point +'} -> $junk->{'z_point'} --> $junk->{'error'}\n"; }
    If @data is large you may want to use a Schwartzian Transform (or other method of improving sort performance).

    BTW, @data would more properly be called a "list of hashes".

      What lhoward said, but if the values in $junk{count} are numerical values (as seems likely), the first half of the sort should use the spaceship comparison operator: {$b->{count} <=> $a->{count} Otherwise 12 will sort before 2.

      Note that quotes inside the curly braces are not needed.

      And discussions of the Merlynian Schwartzian Transform can be found here, here, and here. (Heady stuff but great when you need it!)

      I believe your or binds too loosely so you get:
      ( sort {$b->{'count'} cmp $a->{'count'} ) or $b->{'type'} cmp $a->{'type'}} @data
      (Note the parentheses above indicating the binding).

      What you want is:

      sort {$b->{'count'} <=> $a->{'count'} || $b->{'type'} cmp $a->{'type'}} @data
      Which binds more tightly. I also flipped the comparison operator for count since as dvergin pointed out above you probably want a numeric comparison for count.

      -ben

        I don't think so... my test program:
        #!/usr/bin/perl -w use strict; my @a; push @a,{x=>1,y=>1}; push @a,{x=>2,y=>1}; push @a,{x=>1,y=>2}; push @a,{x=>2,y=>3}; push @a,{x=>2,y=>2}; foreach(sort {$a->{x} <=> $b->{x} or $a->{y} <=> $b->{y}} @a){ print " $_->{x} $_->{y}\n"; }
        prints the correct results:
        $ ./sort.pl 1 1 1 2 2 1 2 2 2 3
        Thanks for your response. I thought that || was the same as or. How are they different? Txs
      Just wanted to thank you. This is working great.
      Thanks for the info. This is the first program I have written using hashes. Thanks again for your help.
Re: how do I sort on two fields in a hash table?
by a (Friar) on Mar 16, 2001 at 11:32 UTC
    Another route is to rethink your data structure; if you have an array of hashs w/ each hash having 2 sort/index fields, try a hash (w/ count as the key) where the values are arrays of hashs which have type, a_point, z_point, error as members, something like:
    foreach $count ( sort keys %data ){ # there's a better notation for this but $data{$count}->@ # never works for my foreachs ;-> foreach $type_hash ( @{ $data{$count} } ) { print "$count"; # just to make it easier to see, loop through the keys # for the type hash in your order. NB type_hash is # hash ref ($$type_hash) foreach $val ( (qw(type a_point z_point error) ) ) { print " --> $$type_hash{$val}"; } # foreach val print "\n"; } # foreach type_hash } # foreach count
    This may be way off, for your data - the thought is that if you're having to work this hard to sort your data structure, you should think about changing your struct. Assuming you have data like:
    count|type|a point|z point|error
    3    |bz  |6.0    |7.2    |none
    3    |boz |2.0    |1.1    |off by one
    5    |bz  |3.5    |2.2    |ugly
    
    you could:
    while(<DATA>) { chomp; my ($count, $type, $a_point, $z_point, $error) = split(/\|/); push @{ $data{$count} }, {type => $type, a_point => $a_point, z_point => $z_point, error => "$error", }; } # while DATA

    a