in reply to Build Sort dynamically

There is an old hack from tye at Re: Sorting on Section Numbers that is worth considering. You just pad the numbers to fixed width, and then sort alphabetically. That will sort both numbers and text correctly in most cases. Plus as a bonus it will sort "foo2" before "foo12", which is usually what people want to do.

But if you're building the sort sub dynamically the simplest thing to do is to build an array of sort closures, then combine them into a sort sub. Re: Fun with complex sorting on arbitrary criteria list. can show you how to combine the closures into a single sort subroutine. (Note that there I don't use the ($$) prototype and here I do. That is a good habit because it is very useful to be able to set up the sort routine in one package and call it in the other. It doesn't work on Perl 5.005, but that is not widely used any more.) As for the individual sort subs, they might look something like this (all code is untested):

my $field_index = $position{$field}; if ("text" eq $type{$field}) { if ("asc" eq $order{$field}) { push @sort_sub, sub ($$) { $_[0][0][$field_index] cmp $_[1][0][$field_index]; }; } else { push @sort_sub, sub ($$) { $_[1][0][$field_index] cmp $_[0][0][$field_index]; }; } } else { if ("asc" eq $order{$field}) { push @sort_sub, sub ($$) { $_[0][0][$field_index] <=> $_[1][0][$field_index]; }; } else { push @sort_sub, sub ($$) { $_[1][0][$field_index] <=> $_[0][0][$field_index]; }; } }
And then the actual sort would use a Schwartzian transform like this:
my @sorted_list = map {$_[1]} sort $sort_routine map {[[split $_, "|"], $_]} @list;
Update: I had a trailing comma after sort $sort_routine. Now fixed.

Replies are listed 'Best First'.
Re^2: Build Sort dynamically
by AnomalousMonk (Archbishop) on Aug 24, 2008 at 05:17 UTC
    You are prototyping your anonymous subroutine definitions. The perlsub documentation seems to say that prototypes have no effect with such subs. Can you say what is happening here? Is this something peculiar to the sort builtin?

    Also, sort doesn't seem to work properly with a code reference (which I take $sort_routine to be) as its first argument (tested on 5.8.2). Is this something new in 5.10?

      The not working correctly was an extra comma. Sorry for the typo, but as I said, all code is untested. I've removed that and it should work better.

      The prototype is explained in the documentation to sort. If you provide the prototype, then the arguments are passed in @_. If you don't, then they are passed in $a and $b. Passing in $a and $b is marginally more efficient, but the problem comes when you create a sort routine in one package and call it in another. The package in which $a and $b are set is the one you call the sort routine in, and not the one you created the subs in. Which can become tricky. With the prototype, those cross-package problems go away.

      This is potentially important in this case because depending on your overall code organization you may want to have each field know how to sort its own datatype. (Add a date-time field which could be in another language and it will quickly become apparent why you might want a more sophisticated organization.) Which would result in sort routines being spread out across packages.

        Ooo, neat! I tried my little test without the extraneous comma and it worked as advertised.

        I had thought the discussion regarding prototyping the subroutines supplied to sort applied only to named subroutines. This insight may come in very handy! Thanks for the tip. I will have to re-read the sort docs more closely (but tomorrow -- signing off soon).

        (Likewise, the discussion of prototyping in perlsub needs another perusal -- or is it perl-rusal?)