Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister

sorting a hash by keys, according to preference

by vindaloo (Novice)
on Apr 13, 2005 at 03:46 UTC ( #447250=perlquestion: print w/ replies, xml ) Need Help??
vindaloo has asked for the wisdom of the Perl Monks concerning the following question:

I would like to sort a hash not in any of the ways I have seen in tutorials (therefore I am open to advice on rethinking the problem too). I need to sort these keys, in this order:

  • start
  • stop
  • step

sort() on these strings will obviously return: start, step, stop, without my own sorting function. I have read japhy's Resorting to Sorting article and have attempted to apply some of those approaches, but I need a hand.

use strict; use warnings; use diagnostics; my @aoh; push @aoh, { "start" => .4, "stop" => .6, "step" => .1 }; push @aoh, { "start" => .3, "stop" => .5, "step" => .1 }; push @aoh, { "start" => .0, "stop" => .2, "step" => .1 }; push @aoh, { "start" => .5, "stop" => .7, "step" => .1 }; my $aoh = \@aoh; for my $point ( @{$aoh}) { #for my $type ( sort keys %$point ) { for my $type ( sort by_pref keys %$point ) { print "$type\t"; } print "\n"; } sub by_pref { $a cmp $b; #advice? }

P.S. I am not interested in forcing the insertion order using Tie::IxHash for a few different reasons.

Comment on sorting a hash by keys, according to preference
Select or Download Code
Replies are listed 'Best First'.
Re: sorting a hash by keys, according to preference
by dave0 (Friar) on Apr 13, 2005 at 04:02 UTC
    If you know all of the keys you want to reference, and the order you'd like them in, why not just avoid the sort in the first place?
    for my $point ( @{$aoh} ) { for my $type ( qw ( start stop step ) ) { print "$type = " . $point->{$type} . "\t"; } print "\n"; }
      Thanks for the pointers! I was sure it had to be more simple than a Shwartzian Transform...
Re: sorting a hash by keys, according to preference
by Tanktalus (Canon) on Apr 13, 2005 at 03:57 UTC

    First off, I'm not sure what order you expect your output to be. I'm going to guess that you want something like this:

    .4 .6 .1 .3 .5 .1 .0 .2 .1 .5 .7 .1
    In which case, I would create an array with the order of keys you want to go through:
    my @keys = qw(start stop step); for my $type (@keys) { print $point->{$type}, "\t"; }
    If some of the keys may not appear, you could skip them:
    my @keys = qw(start stop step); for my $type (@keys) { next unless exists $point->{$type}; print $point->{$type}, "\t"; }
    Or, in the spirit of TMTOWTDI, you could have a hash of weightings:
    my %weights = ( start => 10, stop => 20, step => 30 ); for my $type ( sort { $weights{$a} <=> $weights{$b} } keys %$point ) { print $point->{$type}, "\t"; }
    But I like the array better. No actual sorting required. The hash weights are more useful when you have great numbers of possible keys to sort, but only a few keys will be in any given hash. In this case, you may not want to generate the weights by hand, but generate them in code:
    my %weights = do { my $i = 0; map { $_ => $i++ } qw(start stop step ot +her stuff in order here) };
    Hope that helps.

Re: sorting a hash by keys, according to preference
by crashtest (Curate) on Apr 13, 2005 at 04:54 UTC
    For general reference, if you do need to sort strings in a predetermined, non-alphabetical order, I believe the following is a common form:
    sub by_pref { ($b eq 'start') <=> ($a eq 'start') or ($b eq 'stop') <=> ($a eq 'stop') or ($b eq 'step') <=> ($a eq 'step') }
    It's fairly readable (as in the sort order is visibile at a glance)... and more importantly it's fun to figure out how it works. At least it was for me the first time I came across this idiom, and it's a rare opportunity to use the spaceship operator (check perlop if you haven't seen it before).

    Briefly, each of the three branches of the or expression only evaluate to true if exactly one of $a or $b is equal to the string being checked in that line. For instance, ($b eq 'start') <=> ($a eq 'start') evaluates to (1) <=> (1) #(== 0) if both $a and $b equal "start", and to (0) <=> (0) #(== 0) if both aren't. So then the next part of the or chain is checked. If exactly one of $a or $b is equal to "start" however, the appropriate comparison is returned. If $a eq $b, the entire expression returns 0, as it's supposed to. It's easier to see how it works if you step through it with specific examples.

    In this case, hardcoding the order as Tanktalus and dave0 suggest makes more sense. That way, you avoid sorting altogether.

      It turns out I was after two things in my question: How to sort the hash the way I wanted and how to sort the hash using a common hand-crafted sort function with the space-ship operator. I am glad to see these working examples for both for my future sorting needs. I may just play around with this sort function to get a handle on how it works.

      Thanks again!

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://447250]
Approved by moot
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (6)
As of 2016-04-29 23:11 GMT
Find Nodes?
    Voting Booth?
    :nehw tseb si esrever ni gnitirW

    Results (441 votes). Check out past polls.