1820243 Members
2497 Online
109621 Solutions
New Discussion юеВ

Re: Using Sort in PERL

 
SOLVED
Go to solution
Jeff Colyer
Advisor

Using Sort in PERL

I'm trying to sort an alphanumeric text in perl and it's sorting like this:

boo1
boo10
boo2
foo1
foo2
foo20
foo3
foo4
...

Is there a way to get perl to sort alphanumically so it sorts like
boo1
boo2
...
boo10
??

I tried looking at some sort man pages but I can't find options like the unix sort.
7 REPLIES 7
James R. Ferguson
Acclaimed Contributor

Re: Using Sort in PERL

Hi Jeff:

Assuming data as shown (3-letters followed by digits), this would work:

# perl -le '@a=qw(boo2 foo1 boo10 foo2 foo20 foo3 foo4);@a=sort{substr($a,0,3) cmp substr($b,0,3)||substr($a,3) <=> substr($b,3)} @a;print "@a"'

Regards!

...JRF...
H.Merijn Brand (procura
Honored Contributor

Re: Using Sort in PERL

Ideal for a so-called Swarzian Transform:

my @sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] || $a->[2] <=> $b=>[2] }
map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ]
@unsorted;

Enjoy, Have FUN! H.Merijn
Enjoy, Have FUN! H.Merijn
Jeff Colyer
Advisor

Re: Using Sort in PERL

procura,
If I use the approach you mentioned, how would I fit it in here:
my $temp;
for $temp ( sort keys %scalelist) {
my $rec = $scalelist{$temp};
do {

I'm trying to sort the keys which can be any length numeric string followed by a numberic string.
Ex:
xxxxx1
xxxxx10
xxxxx2
yyy1
yyy2
yyy20
yyy3

I'm just not sure how to put it in this statement. Maybe create a sub called NewSort and put that sub in place of the sort above???

Thanks in advance Jeff.
James R. Ferguson
Acclaimed Contributor

Re: Using Sort in PERL

Hi (again) Jeff:

Merijn's solution is by far more generalized for your data than mine, let alone much more efficient.

Assuming that you have collected your data in a hash called 'scalelist' with keys that look like your original post, do something like:

@sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] || $a->[2] <=> $b->[2] }
map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ] }
keys %scalelist;

foreach $key (@sorted) {
print "$key => $scalelist{$key}\n";
}

Regards!

...JRF...
Jeff Colyer
Advisor

Re: Using Sort in PERL

James,
I got this error when trying that:

Useless use of spaceship operator in void context at line 183.
Sort subroutine didn't return a numeric value at line 185.

The lines correspond with the use of "map" syntax.
H.Merijn Brand (procura
Honored Contributor
Solution

Re: Using Sort in PERL

I not only forgot the last brace, but also typoed in the sort (=> should be ->) :/

my @sorted = map { $_->[0] }
sort { $a->[1] cmp $b->[1] || $a->[2] <=> $b->[2] }
map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ] }
@unsorted;


Mea culpa.

lt09:/home/merijn 109 > cat test.txt
boo1
boo10
boo2
foo1
foo2
foo20
foo3
foo4
lt09:/home/merijn 110 > cat test.pl
#!/pro/bin/perl

use strict;
use warnings;

my @unsorted = <>;

my @sorted =
map { $_->[0] }
sort { $a->[1] cmp $b->[1] || $a->[2] <=> $b->[2] }
map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ] }
@unsorted;

print @sorted;
lt09:/home/merijn 111 > perl test.pl test.txt
boo1
boo2
boo10
foo1
foo2
foo3
foo4
foo20
lt09:/home/merijn 112 >

OK, let's also explain.

@unsorted is your array

perl's 'map' transforms each element of the list passed to something else (that can be anything):

@high = map { $_ + 10 } 1..4;

In above case we pass the list (1, 2, 3, 4) to map. It gets each element in turn (in $_), and adds 10 to it, passing it along. So now @high is (11, 12, 13, 14)

For a S-T sort, the first map converts to an anonymous array

@both = map { [ $_, $_ + 10 ] } 1..4;

will transform the list 1,2,3,4 to ([1,11],[2,12],[3,13],[4,14])
It is common to put the original value in the first element of the list, and the values to sort on in the rest

Now the sort doesn't ket the original values, but a list of lists, where it can efficiently sort on the precalculated elements in the list, so the calculations only have to be done once, instead of on every comparison

map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ] }

$_ is your value
($_ =~ m/..../) is evaluated in list context, and thust returns the caught elements in $1 and $2. In even terser mode, I could have left out $_ =~, since that is the default for m//.
If that fails, it catches nothing, causing errors in the compare part, which is why I also added "", and 0 to the list. For good matches, these two are ignored in the sort phase, but are a catch-net for failed matches. So

map { [ $_, ($_ =~ m/^([a-z]+)(\d+)/), "", 0 ] } qw( boo1 boo12 frubble4 blast99 );

would return

[ "boo1", "boo", 1 ], [ "boo12", "boo", 12 ], [ "frubble4", "frubble", 4 ], [ "blast99", "blast", 99 ]

As you see this list contains all you need for a proper sort

the sort part sorts the anonymous lists on the second element (the letter part) with cmp, and if that is equal, on the numeric part with <=>

([ "boo1", "boo", 1 ], [ "boo12", "boo", 12 ], [ "blast99", "blast", 99 ], [ "frubble4", "frubble", 4 ])

That last map, just gets you the first element of the list out again, throwing the rest away

("boo1", "boo12", "blast99", "frubble4")

The Swartzian Transform is also described in the perl FAQ:

# perldoc -q 'How do I sort an array by (anything)?'

Enjoy, Have FUN! H.Merijn
Enjoy, Have FUN! H.Merijn
Jeff Colyer
Advisor

Re: Using Sort in PERL

Thanks Merijn. Your explanation really helped me out. I was able to get it to work with your explanation and code snippet.