-2

I have keys in a hash as follows: AB3, AB1, AB2 and so on. I would like to sort the hash by the keys. How do I do this in perl?

A related issue is how can I sort the keys so that letters and numbers appear in the correct order?

G. Cito
  • 6,210
  • 3
  • 29
  • 42
  • 1
    Please see [how to ask](http://stackoverflow.com/help/how-to-ask) to improve your question... – jkalden Oct 27 '15 at 12:00

3 Answers3

4

You need the sort function:

foreach my $key ( sort keys %hash ) {
    print "$key => $hash{$key}\n"; 
}

You can't easily maintain hashes as sorted structures, as they just don't work that way. See: perldata for more about how hashes work.

Edit: One of the niceties of sort in perl though, is that it lets you specify a function. This function should take $a and $b and return -1, 0, +1 depending on if they're before or after.

cmp does that for alphabetical. <=> does that numerically. And best of all, when combined with || you can daisy chain the criteria, because -1 or 1 are 'true' but 0 is false.

Something like this for example (borrowed the key list from another post to illustrate):

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

use Data::Dumper;

my @keys = qw/ AB3 AB1 AB4
    CD5 CD107 CB8
    AC1 AC5 AC33
    BA84 CB11 CA233/;


sub lex_num {
    #split the keys into "word" and "digit" elements. 
    my @a_keys = $a =~ m/([A-Z]+)(\d+)/i;
    my @b_keys = $b =~ m/([A-Z]+)(\d+)/i;

    return ( $a_keys[0] cmp $b_keys[0] 
          || $a_keys[1] <=> $b_keys[1] );
}

print join( "\n", sort lex_num @keys );

This will sort the first bit lexically, the second bit numerically. If you've more combinations of letters and numbers then this won't work, but you can do e.g. a split and a for loop.

Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • Yours is the canonical answer :-) (closest to the one from from the FAQ) I tried combining the basics with the sorting order issue raised by @Toto using a module. Sobrique and Toto: Is there a shorter ""idiom" that would do natural sorting or is using a module defensible until one groks the code in Toto's response? – G. Cito Oct 27 '15 at 19:01
  • What do you mean by natural sorting? If I've a dual-key type sort (like numbers/letters) then usually I'll just do a regex to capture the two different values. – Sobrique Oct 27 '15 at 19:13
  • OK so more or less do as @Toto has done? As I read it, `Sort::Naturally` is more or less hiding the block you can pass to the `sort()` function inside a module and providing a convenience function to wrap things up nicely. – G. Cito Oct 27 '15 at 19:17
  • 1
    Pretty much. Added my own illustration using the longer key list. – Sobrique Oct 27 '15 at 19:29
  • That's a bit nicer looking than @Toto ;-) I knew there was a way `||` could shorten things. In any case it is nice to know what goes on behind the scenes with something like `Sort::Naturally`. Cheers. – G. Cito Oct 27 '15 at 19:42
  • 1
    Modules are generally beneficial, but I find it really does help to understand what's actually going on behind the scenes - I assume `Sort::Naturally` does something a bit more comprehensive than in the above, but this is sufficient given the sample data. – Sobrique Oct 27 '15 at 19:46
1

If your keys are defined by non digits followed by digits, you could do:

my %h = (
  CD45 => 4,
  AB1 => 1,
  AB22 => 3,
  AB5 => 2,
);
sub mySort {
  my ($xa,$ya) = $a =~ /^(\D+)(\d+)$/;
  my ($xb,$yb) = $b =~ /^(\D+)(\d+)$/;
  return -1 if $xa lt $xb;
  return +1 if $xa gt $xb;
  return $ya <=> $yb;
}
for (sort { mySort } keys %h) {
  say "$_ => $h{$_}";
}

Output:

AB1 => 1
AB5 => 2
AB22 => 3
CD45 => 4
Toto
  • 89,455
  • 62
  • 89
  • 125
  • Good answer to what is often the difficult part of a sorting operation. Unfortunately we have to guess at what problem the original poster is trying to solve. – G. Cito Oct 27 '15 at 17:06
0

Here's how you might combine the two approaches and shorten your code by using a CPAN module - in this case Sort::Naturally (I have made the hash keys a bit more complex for illustrative purposes):

use Sort::Naturally ;

my @keys = qw/ AB3 AB1 AB4 
               CD5 CD107 CB8 
               AC1 AC5 AC33 
               BA84 CB11 CA233/ ;

# make a hash from the keys with "whatever" as value:
my %hash;
%hash = map { $_ => $hash{$_} = 'whatever' } @keys ; 

# Auto-magically naturally sort alphanumerically
# A module based approach to @Toto's solution:
for ( nsort keys %hash ) { print "$_ => $hash{$_} \n" };

@Sobrique has responded with the basic perl approach to sorting a hash by its keys, and @Toto has shown how you can sort those keys in a specified order using your own subroutine as an argument in the block { } following the builtin sort() function.

References:

Community
  • 1
  • 1
G. Cito
  • 6,210
  • 3
  • 29
  • 42