Update Note @Miller's comment below on some shortcomings of the Sort::Naturally
module.
What you are asking for is a relatively complicated sort that splits each string into alphabetical and numeric fields, and then sorts the letters lexically and the numbers by value.
The module Sort::Naturally
will do what you ask, or you can write something like this. You appear to have ignored the X
key, so I have sorted it to the end using a case-independent sort.
use strict;
use warnings;
my %hash = map { $_ => 1 } qw(
chr22 chr20 chr19 chr13 chr21 chr16 chr12 chr10 chr18
chr17 chrY chr5 chrX chr8 chr14 chr6 chr3 chr9
chr1 chrM chr11 chr2 chr7 chr4 chr15
);
my @sorted_keys = sort {
my @aa = $a =~ /^([A-Za-z]+)(\d*)/;
my @bb = $b =~ /^([A-Za-z]+)(\d*)/;
lc $aa[0] cmp lc $bb[0] or $aa[1] <=> $bb[1];
} keys %hash;
print "$_\n" for @sorted_keys;
output
chr1
chr2
chr3
chr4
chr5
chr6
chr7
chr8
chr9
chr10
chr11
chr12
chr13
chr14
chr15
chr16
chr17
chr18
chr19
chr20
chr21
chr22
chrM
chrX
chrY
Using the Sort::Naturally
module (you will probably have to install it) you could write this instead.
use strict;
use warnings;
use Sort::Naturally;
my %hash = map { $_ => 1 } qw(
chr22 chr20 chr19 chr13 chr21 chr16 chr12 chr10 chr18
chr17 chrY chr5 chrX chr8 chr14 chr6 chr3 chr9
chr1 chrM chr11 chr2 chr7 chr4 chr15
);
my @sorted_keys = nsort keys %hash;
print "$_\n" for @sorted_keys;
The output is identical to the above.