kiat has asked for the wisdom of the Perl Monks concerning the following question:

Hi monks,

I'm trying to sort number-letter sequences in descending order but my code doesn't produce the desired result.
use strict; my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = join("\n", sort {$b <=> $a} @array); print foreach @sorted; # Output 21sg 12kf 9vn 9cn 2ba 2aa
The desired output is:

21sg
12kf
9cn
9vn
2aa
2ba

In other words, I need the sorting to take into account the letters as well.

How do I accomplish that? I look forward to reading your solutions. Thanks in anticipation =)

cheers

Replies are listed 'Best First'.
(jeffa) Re: Helping with sorting
by jeffa (Bishop) on Apr 02, 2003 at 01:41 UTC
    How about a Schwartzian Transform?
    use strict; use warnings; my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = map { join('',@$_) } sort { $b->[0] <=> $a->[0] or $a->[1] cmp $b->[1] } map { /(\d+)(\w+)/;[$1,$2] } @array ; print "$_\n" foreach @sorted; __END__ 21sg 12kf 9cn 9vn 2aa 2ba
    Update: be sure and read japhy's Resorting to Sorting. Especially the part about the Guttman Rosler transforms. (i'll leave that implementation of this particular problem as an exercise for another monk ;))

    Update2: just noticed that the map after the sort could be written more consisely as:

    map { [/(\d+)(\w+)/] } @array
    Update3: fixed typo (s/b->\[1/a->[1/) ... thanks Enlil :)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Thanks to all! I've not only solved the problem I was facing but I'm also learning a lot =)
      I can't make out where to apply your update3 in your code...if it at all applies to the problem produced in the output "9cn", should be "9nc". Update4?? :) Chris
Re: Helping with sorting
by Enlil (Parson) on Apr 02, 2003 at 02:03 UTC
    Heres another way to do it. Though it will give warnings with -w or the warnings pragma.
    use strict; my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = sort {$b <=> $a || $b cmp $a } @array; print $_,$/ foreach @sorted;

    update: the above does both (letters and numbers) in descending order what I think the OP wanted was descending numbers ascending letters. so the following should work:

    my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = sort {$b <=> $a || $a cmp $b } @array; print $_,$/ foreach @sorted;

    -enlil

      Wow! (This is the kind of feeling you don't have very often) This solution is really graceful, simple and straight.

      Enlil played at least three very neat tricks here:
      1. He forced the number portion to be extracted and compared. (this gives some warnings, but compare to the elegance he showed us in this one liner, those warnings are really nothing)

        If you really want to avoid the warnings, do this:
        my @sorted = sort {($b =~ /(\d*)/)[0] <=> ($a =~ /(\d*)/)[0] || $a cmp + $b} @array;
      2. That || triggered the second condition to be evaluated, if the first condition evaluates to false, two elements have the same number portion are ordered by the string as a whole.
      3. If two elements have the same number portion, then logically, to order them by the string portion following the number portion results the same sequence as to order by the whole string.
      There's just a minor problem with this - granted the sort works with the data supplied. How about text with leading zeros?
      2ah 02bl 01hz
      I guess that to answer the question of leading zeros, we need to know what the specified requirements are. Still, it's another test scenario for you.
      Note: This will only work because the numbers are before the letters. If the letters were first, the numeric comparison would be a no-op.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: Helping with sorting (natural)
by tye (Sage) on Apr 02, 2003 at 06:31 UTC

    I like several methods found under natural sort. I'll throw together a new one since I haven't done such in a while:

    my @array = qw( 2aa 2ba 12kf 9cn 9vn 21sg ); my @idx= do { my @key = @array; s[(\d+)][ pack "N", $1 ]ge for @key; sort { $key[$a] cmp $key[$b] } 0..$#key; }; my @sorted= @array[@idx]; print "@sorted\n";
    outputs
    2aa 2ba 9cn 9vn 12kf 21sg

                    - tye
Re: Helping with sorting
by tekkie (Beadle) on Apr 02, 2003 at 15:12 UTC
    On a side note, I couldn't help but notice that these lines probably aren't doing what you think:
    my @sorted = join("\n", sort {$b <=> $a} @array); print foreach @sorted;
    What it looks like you want is for @sorted to be a list of your sorted values, which at the moment, it's not...

    When you use join(), it returns a single scalar with your list values delimited by the characters you specify.

    Try:
    my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = join("\n", sort {$b <=> $a} @array); print $#sorted; # Prints the index of the last element in the array @sorted = sort {$b <=> $a} @array; print $#sorted;
    And you'll see what I mean.
Re: Helping with sorting
by cLive ;-) (Prior) on Apr 02, 2003 at 17:10 UTC
    timtowtdi :)
    my @array = qw(2aa 2ba 12kf 9cn 9vn 21sg); my @sorted = sort { $a.$b =~ /(\d+)([a-z]+)(\d+)([a-z]+)/; $1 <=> $2; $3 cmp $4; } @array; print join "\n", @sorted;
    cLive ;-)
Re: Helping with sorting
by sfink (Deacon) on Apr 03, 2003 at 05:31 UTC
    For YAWWTDI (Yet Another Wrong Way To Do It):
    my @sorted = sort { sprintf("%010d%s",$a,$a) cmp sprintf("%010d%s",$b, +$b) } @array;
    Except that, as noted elsewhere, you are strangely doing a descending numerical sort with an ascending alphabetic sort. So we need to flip around one of those sorts. Easy enough, if you don't mind limiting your numeric range a little bit:
    my @sorted = sort { sprintf("%08d%s",99999999-$a,$a) cmp sprintf("%08d +%s",99999999-$b,$b) } @array;
    or, better but still quite Wrong:
    my @sorted = sort { sprintf("%010u%s",-$a,$a) cmp sprintf("%010u%s",-$ +b,$b) } @array;