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

This question falls into the category: Things I used to know how to do but have apparently forgotten.

According to the Camel book (3rd ed, p 789-90), when using a subroutine to provide the formula for a sort operation,

sort USERSUB LIST
"USERSUB may be a scalar variable name ..., in which case the value provides either a symbolic or a hard reference to the actual subroutine to use." Thus, the following DWIMs:

my $mysortref = sub { lc($data{$a}[0]) cmp lc($data{$b}[0]) || lc($data{$a}[1]) cmp lc($data{$b}[1]) }; @results = sort $mysortref (keys %data);

For a complete example -- sorting by last name and breaking ties by first name (both in ascending order) -- see here:

my (%data, @results); while (<DATA>) { my @temp = split; $data{$temp[2]} = [@temp]; } my $mysortref = sub { lc($data{$a}[0]) cmp lc($data{$b}[0]) || lc($data{$a}[1]) cmp lc($data{$b}[1]) }; @results = sort $mysortref (keys %data); print "@{$data{$_}}\n" for (@results); __DATA__ HERNANDEZ HECTOR 456791 SAMSON 0217 2001-07-25 1963-08-0 +1 VASQUEZ JOAQUIN 456789 SAMSON 0209 1990-11-14 1970-03-2 +5 JONES TIMOTHY 803092 LAVER 0103 2001-03-19 1969-06-2 +9 SMITH BETTY_SUE 698389 SAMSON 0211 1992-01-23 1949-08-1 +2 VASQUEZ LEONARDO 456788 LAVER 0107 1990-08-23 1970-15-2 +3 SMITH HAROLD 359962 TRE 0111 2001-07-19 1973-10-0 +2 VASQUEZ ADALBERTO 786792 LAVER 0104 2001-07-26 1973-08-1 +7 VASQUEZ JORGE 456787 LAVER 0105 1986-01-17 1956-01-1 +3 VAZQUEZ TOMASINA 456790 LAVER 0110 1980-11-14 1960-14-0 +2 WILSON SYLVESTER 498703 LAVER 0110 1983-04-02 1953-06-2 +2 VASQUEZ ALBERTO 906786 TRE 0111 2001-07-15 1953-02-2 +8

But suppose that instead of hard-coding the sort formula I want to be able to build it up on the fly based on a per-call basis. How would I go about that?

For instance, suppose that for some reason I wanted to sort by last name in ascending order but break ties by sorting by first name in ascending order. And suppose further that I wanted to do so by having the anonymous subroutine altered due to configuration information, not due to changes in coding.

Can anyone steer me in the correct direction? I've checked the Camel book and the Perl Cookbook, but not discovered an answer.

Thank you very much.

Jim Keenan

Replies are listed 'Best First'.
Re: Building a sorting subroutine on the fly
by brian_d_foy (Abbot) on Nov 29, 2005 at 01:21 UTC

    Don't you just want to create the subroutine on the fly and wrap it in something that you can give to sort?

    For each piece of the sort you have a little subroutine that tries just that piece. Store all those little subroutines in an array. You'll call them in order and return their value unless they return 0, in which case you try the next subroutine reference in the array.

    my $sort_sub = do { my @subs = ... stuff to build array sub { foreach my $sub ( @subs ) { my $result = $sub->(); return $result if $result; } }; };
    Update: Duh. $a and $b are globals so I don't need to pass them in the sort subroutine.
    --
    brian d foy <brian@stonehenge.com>
    Subscribe to The Perl Review
      Store all those little subroutines in an array.

      If I understand you correctly, that would entail changing my original code only with respect to what gets assigned to $mysortref:

      my $mysortref = do { my @littlesubs = ( sub { lc($data{$a}[0]) cmp lc($data{$b}[0]) }, sub { lc($data{$a}[1]) cmp lc($data{$b}[1]) }, ); sub { foreach my $sub (@littlesubs) { my $result = $sub->($a, $b); return $result if $result; } }; }; @results = sort $mysortref (keys %data);

      But I don't see where that gets me any closer to solving my original problem. To change the sort formula I still, AFAICT, have to change the hard-coded subs which are now elements in @littlesubs.

      jimk

        sub sort_maker { my @subs = @_; sub { foreach my $sub (@subs) { my $result = $sub->(); # you use $a and $b in your littlesubs - +no need to pass in globals. return $result if $result; } } } my $sorter = sort_maker( sub { lc($data{$a}[0]) cmp lc($data{$b}[0]) }, sub { lc($data{$a}[1]) cmp lc($data{$b}[1]) }, ); @results = sort $mysortref (keys %data);

        It's left as an excersise to the reader on how to convert your query language into perl sort functions. Translation: I'd help further, but I don't know how you know what order to sort in.

        Don't put hard-coded subs in @littlesubs. Build up that array dynamically from a bunch of subroutines you've already defined.

        --
        brian d foy <brian@stonehenge.com>
        Subscribe to The Perl Review
Re: Building a sorting subroutine on the fly
by steveAZ98 (Monk) on Nov 29, 2005 at 01:50 UTC
Re: Building a sorting subroutine on the fly
by salva (Canon) on Nov 30, 2005 at 09:22 UTC
    Sort::Key allows you to easyly create multikey sorting functions on the fly, for instance:
    use Sort::Key qw(multikeysorter); # ascending last, ascending first: $sorter = multikeysorter(sub { lc $_->[0], lc $_->[1] }, qw(string, string)); @sorted = &$sorter(values %data) # ascending first, descending last: $sorter = multikeysorter(sub { lc $_->[1], lc $_->[0] }, qw(string, -string)); @sorted = &$sorter(values %data)