Reputation: 47
I have the following array of strings:
Expt5_Expt12
Expt5_Expt1
Expt12_Expt2
Expt11_Expt8
Expt1_Expt2
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
I want to sort these strings by the first number and in second priority by the second number so that I have a list like that:
Expt1_Expt2
Expt5_Expt1
Expt5_Expt12
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
Expt11_Expt8
Expt12_Expt2
I only found solutions to sort only by the first number OR by the second. I tried some things with regex-expressions and sort-function but I didn't come to a solution.
Upvotes: 0
Views: 956
Reputation: 53478
Sorting by function is really easy. The function has to return -1, 0 or 1 depending if $a
and $b
are before or after. (As noted in comments - it can be any positive or negative value - the key point is whether the elements are before or after each other).
$a
and $b
are 'special' variables used specifically for perl and sorting. They therefore don't need to be declared, and are a really bad idea to use for other stuff in your code. But then, who uses single letter vars anyway?
So with your values:
#!/usr/bin/env perl
use strict;
use warnings;
sub custom_sort {
my ( $a1, $a2 ) = ( $a =~ m/(\d+)/g ); #extract the numeric elements
my ( $b1, $b2 ) = ( $b =~ m/(\d+)/g );
return ( $a1 <=> $b1 #return the result of this comparison
|| $a2 <=> $b2 ); #unless it's zero, then we return the result of this.
}
my @list = <DATA>;
print sort custom_sort @list;
__DATA__
Expt5_Expt12
Expt5_Expt1
Expt12_Expt2
Expt11_Expt8
Expt1_Expt2
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
You can make this more concise, but the essence is this:
||
operator - so that if $a1 <=> $b1
is zero, it evaluates the second part of the expression. <=>
is a 'less than, equal to, greater than' operator which returns -1, 0 or 1 based on numeric comparison. For strings you can use cmp
to do the same thing. (You can print these if you wish to debug how this sort is 'working' for each comparison, which is really handy if you're doing something complicated)
Upvotes: 9
Reputation: 10234
use Sort::Key::Multi;
use Sort::Key::Multi qw(i2_keysort); #i2 means two integer keys
my @sorted = i2_keysort { /(\d+)\D+(\d+)/ } @data;
Upvotes: 1
Reputation: 126722
This is broadly the same solution as others have posted, but it is made more concise by the use of map
use strict;
use warnings;
my @data = <DATA>;
print sort {
my @ab = map [ /\d+/g ], $a, $b;
$ab[0][0] <=> $ab[1][0] or $ab[0][1] <=> $ab[1][1];
} @data;
__DATA__
Expt5_Expt12
Expt5_Expt1
Expt12_Expt2
Expt11_Expt8
Expt1_Expt2
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
Expt1_Expt2
Expt5_Expt1
Expt5_Expt12
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
Expt11_Expt8
Expt12_Expt2
Upvotes: 2