Molag Bal
Molag Bal

Reputation: 47

How to sort an array of strings containing two numbers in perl?

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

Answers (3)

Sobrique
Sobrique

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:

  • extract the first and second values.
  • Then use the || 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

salva
salva

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

Borodin
Borodin

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

output

Expt1_Expt2
Expt5_Expt1
Expt5_Expt12
Expt10_Expt1
Expt10_Expt4
Expt11_Expt1
Expt11_Expt8
Expt12_Expt2

Upvotes: 2

Related Questions