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

Hi monks,

I have an array which contains lots of strings which match the expression: /^(.+)(\d+)\#(\d+)$/
where $1 is always the same.

I want to sort them. The order it is already in is: $2 then $3 but with $3 sorted as a string not as a number (eg 12 before 2)

What I really want is to sort them first by $2 then by $3 but where these are both numerical sorting. How would I go about doing this?

Any assistance would be greatly appreciated.

Replies are listed 'Best First'.
Re: sort an array
by jdporter (Paladin) on Mar 19, 2006 at 07:32 UTC

    Well first of all that regex won't quite work, because the .+ will eat up all the digits, up the last one before the #. To prevent this, use (.+?).

    Now then:

    @sorted = sort { my @a = $a =~ /^(.+?)(\d+)#(\d+)$/; my @b = $b =~ /^(.+?)(\d+)#(\d+)$/; $a[1] <=> $b[1] or $a[2] <=> $b[2] or $a[0] cmp $b[0] } @unsorted;
    However, that's rather inefficient due to the fact that any given string gets parsed each time it's used in a comparison. Using the Schwartzian Transform is one way to alleviate this:
    @sorted = map { $_->[0] } sort { $a->[2] <=> $b->[2] or $a->[3] <=> $b->[3] or $a->[1] cmp $b- +>[1] map { [ $_, /^(.+?)(\d+)#(\d+)$/ ] } @unsorted;

    Maybe that's even easier to make sense of.


    You could get really fancy, and use a module for this. One that comes to (my) mind is Sort::Fields. Here's one way you could do it:
    use Sort::Fields; my $sorter = make_stable_fieldsort( '(\d+)#', [ '2n', '3n', '1' ] ); my @sorted = $sorter->( @unsorted ); # voila
    We're building the house of the future together.
      Thanks JD,

      You're awesome. I tried both of them. The second one had a slight problem (the # needed to be escaped). They both do exactly what I need. I understand the first one better, so I might go with that one.

      myffy

        If you have trouble understanding the second one, you should look up the phrase "Schwartzian transform", when you have some free time, and read up on the technique. Becomming comfortable with this is something that will help you to think like an advanced Perl programmer. It's not just the technique itself, but the paradigms that it represents, notably, the idea of transforming a list, which is a very generally applicable thing and something Perl is very good for doing, so that it is something every really advanced Perl programmer needs to understand.

        However, ad interim, you should use the technique that you understand already.


        Sanity? Oh, yeah, I've got all kinds of sanity. In fact, I've developed whole new kinds of sanity. Why, I've got so much sanity it's driving me crazy.
        the # needed to be escaped

        I don't believe you. Pound signs never need to be escaped in regexes, and it shouldn't make any difference if they are or not. I invite you to show us your exact code — the one that behaves differently depending on whether the pound sign is escaped or not.

        We're building the house of the future together.
Re: sort an array
by salva (Canon) on Mar 19, 2006 at 11:31 UTC
    the fastest way:
    use Sort::Key::Maker iikeysort => qw(integer integer); my @sorted = iikeysort { /(\d+)\#(\d+)$/ } @data;