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

Hi, I have a subroutine that sorts a passed array. The array elements are hash references resulting from a "selectall_arrayref" DBI query against a SQL db. The sort routine selects the sort method (via separate subs) based on input flags. One of the fields I sort with is a "date" field where the dates come in the format "YYYY.MM.DD". I want to numerically sort these, which requires stripping the periods before passing into the sort. Here is what I have now:
sub sort_records { my $sort_routine; ($opt_g =~ /^t/i) ? #flag setting sort order ($sort_routine = \&sort_by_title) : ($sort_routine = \&sort_by_date); foreach (@_) { ($_->{'cleandate'} = $_->{'date'}) =~ s/\D//g; } #do I need this? my @records = map { $_->[0] } sort $sort_routine map { [ $_, $_->{'title'}, $_->{'cleandate'} ] } # or can I do it here? @_; return @records; }
(The sort_by_title and sort_by_date subs are basic sort functions straight from the perl cookbook. Only difference is the order in which they compare the fields).

So my question is, can I get rid of the "foreach" loop and somehow do the same thing right in the map, but without changing the values in the source?

(Any other critiques/suggestions also welcome).

Replies are listed 'Best First'.
Re: Question about map function [ANSWERED]
by ikegami (Patriarch) on Nov 17, 2008 at 22:17 UTC
    Why are you doing
    my @records = map { $_->[0] } sort $sort_routine map { [ $_, $_->{'title'}, $_->{'cleandate'} ] } @_;
    instead of
    my @records = sort $sort_routine @_;

    You're slowing things down and wasting memory.

    Also,

    ($opt_g =~ /^t/i) ? #flag setting sort order ($sort_routine = \&sort_by_title) : ($sort_routine = \&sort_by_date);

    is a complicated way of doing

    $sort_routine = ( $opt_g =~ /^t/i ? \&sort_by_title : \&sort_by_date );

    I've never seen a situation where it made sense to use an assignment inside of the conditional operator (?:).

    So, if your date format is really YYYY.MM.DD (and not YYYY.M.D), you get

    sub sort_records { my $sort_routine ( $opt_g =~ /^t/i ? sub { $a->{title} cmp $b->{title} } : sub { $a->{date} cmp $b->{date} } ); return sort $sort_routine @_; }
      Thanks, this is all very helpful. My perl is very rusty, but slowly coming back. I had just figured out the silliness of doing assignment inside the conditional, and could have saved myself a headache if I had read your response first!

      My sub routines actually had alternative sorts, so they checked the first choice (title or date) and then the second one. I have also now figured out (again, the hard way) that regular string comparison works fine for my date format (not sure why I thought differently). Sigh, live and learn. But my code is getting better.

Re: Question about map function
by ccn (Vicar) on Nov 17, 2008 at 19:45 UTC

    You can perfectly sort "YYYY.MM.DD" values as strings with cmp. You don't need to strip dots. So the foreach loop doesn't needed at all.

    Update: but if you want numeric than

    my @records = map { $_->[0] } sort $sort_routine map { $_->{'date'} =~ /(\d+)\.(\d+)\.(\d+)/; [ $_ +, $_->{'title'}, $1.$2.$3 ] }

    or shorter:

    my @records = map { $_->[0] } sort $sort_routine map { [ $_, $_->{title}, join '', $_->{date} =~ /\d+/g ] }
      Perfect! I do want to sort numerically, hence the need to remove the periods. I tried your second (shorter) solution and it works exactly as needed. I knew there was a way to do it with regexes, but needed superior wisdom to show me the way.

      Much thanks.

        If your months/days are 2-digit zero padded (as YYYY.MM.DD would indicate), then you don't need to sort numerically...alphabetically sorting is fine. Is the format, e.g., "2008.01.01" or "2008.1.1" ??