in reply to Re: Building a sorting subroutine on the fly
in thread Building a sorting subroutine on the fly

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

Replies are listed 'Best First'.
Re^3: Building a sorting subroutine on the fly
by Tanktalus (Canon) on Nov 29, 2005 at 04:17 UTC
    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.

      Translating your code slightly into my style, I'm now working with this:

      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.

      Let me explain the context and interface desired. Returning to my original data sample will be illustrative. Suppose that I have data stored in plain-text files, one record per line, fields delimited by whitespace:

      Suppose further that the columns in this table may be named:

      lastname firstname cno unit ward dateadmission datebirth

      And suppose further that I have a hash (probably stashed in a configuration file) which associates those column names with array elements specifying the order and type of searches to be conducted on each column (i.e., ascending or descending order; alphabetical, ASCII-betical or numerical type). That hash might look like this:

      my %parameters = ( lastname => [ qw( U a ) ], firstname => [ qw( U a ) ], cno => [ qw( U n ) ], unit => [ qw( U s ) ], ward => [ qw( U n ) ], dateadmission => [ qw( D a ) ], datebirth => [ qw( D a ) ], );
      ... where U and D are up and down; a, s and n are the 3 search types.

      Assuming that the data and the column parameters have been used to initialize an object $dp, I want the user to be able to call a method:

      my $results_ref = $dp->sort_by_column( qw{ lastname firstname cno } );

      ... and have $results_ref hold an array of arrays where the outer array is sorted in the order specified in the arguments to sort_by_column. That is, the method will sort the data in the order and using the search-type specified by the column name.

      Under normal circumstances, the user (a cron job or a keyboard operator) will run the same script with the same method calls and arguments every day. But from time to time the database administrator will have to prepare new queries in which, for example, the dates have to be reported in ascending order, rather than descending as in the above. The administrator would then prepare a different %parameters, but the method call would either not change or change so little that the keyboard operator could modify it easily.

      In short, the objective is to go from a list of column names to an array of sort formulas each represented by an anonymous subroutine. If lastname is the first column in the table, then "Sort by last names, alphabetically and in ascending order," should translate into:

      sub { lc($data{$a}[0]) cmp lc($data{$b}[0]) },

      The question, then, is how to effect that translation. Thank you very much.

      Jim Keenan

      Update: Corrected 'readmore' error.

        All depends on how abstract you want to get. Given that you want "the keyboard operator [to be able to] modify it easily," I'd say that you may want it as abstract as possible.

        As you notice, much of the error checking (that parms are actually allowed - e.g., 'foo' isn't a table header) is missing. Feel free to add.

        Also, rather than a big hash, you could build it up using an eval STRING. Up to you - I'm using a large closure instead to probably reduce memory, probably reduce runtime, and definitely reduce the ability for me to screw it up security-wise. That said, I do think that this is probably a scenario that could be made secure for an eval STRING, especially if you start getting many more options on how to do things and those start combining to give exponential degrees of freedom. Here there are only six possible comparators, which is easy enough to hardcode ahead of time. More generically, an eval STRING may ease development and maintenance.

Re^3: Building a sorting subroutine on the fly
by brian_d_foy (Abbot) on Nov 29, 2005 at 05:07 UTC

    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