简体   繁体   中英

How do I sort the date in ascending order using Perl?

The following is my code:

@events=('11/17/1999',  '12/6/1999', '12/23/1999',  
         ' 1/23/2000',  '1/13/2000',  '2/25/2000', 
           '1/5/2000',  '3/18/2000',  '4/10/2000', 
          '3/12/2000', '12/31/1999');

sub sortByDate{
   $adate=$a=~ /(\d{2})\/(\d{2})\/(\d{4})/;    
   $bdate=$b=~ /(\d{2})\/(\d{2})\/(\d{4})/;
   $adate <=> $bdate;
}

@ascending = sort sortByDate  @events;    
print  "@ascending\n";

The expected output should be the date in ascending order.

You were going for:

sub sortByDate{
    my ($am, $ad, $ay) = $a =~ /(\d{2})\/(\d{2})\/(\d{4})/;    
    my ($bm, $bd, $by) = $b =~ /(\d{2})\/(\d{2})\/(\d{4})/;   
    $ay <=> $ay || $am <=> $bm || $ad <=> $bd
}

If you were to rearrange the date into the form yyyymmdd , you could simply use a lexicographical sort.

I believe this is the fastest solution:

my @sorted_events =
    map { substr($_, 8) }
    sort
    map { sprintf('%3$04d%1$02d%2$02d%4$s', split(qr{/}, $_), $_) }
    @events;

Convert to Time::Piece first, then sort works exactly like you'd expect.

#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;

my @events=('11/17/1999',  '12/6/1999', '12/23/1999',  
         '1/23/2000',  '1/13/2000',  '2/25/2000', 
           '1/5/2000',  '3/18/2000',  '4/10/2000', 
          '3/12/2000', '12/31/1999');

foreach my $event ( @events ) {
   $event = eval { Time::Piece->strptime($event, "%m/%d/%Y" )} || $event;
}

print join "\n", sort { $a <=> $b }  @events;
print "\n";
print join "\n", map { $_ -> strftime("%Y-%m-%d") } sort { $a <=> $b } @events;

Note - in the above, if strptime fails, it stays the original string. This will cause a warning in sort because you're not sorting numerically any more.

Try the following trivial sort function.
WARNING: It conducts no data format check whatsoever

sub sortByDate{ 
  my @a = split(/\//,$a);
  my @b = split(/\//,$b);
  return $a[2]<=>$b[2] || $a[0]<=>$b[0] || $a[1]<=>$b[1];
}

Have you looked at the date manipulation modules available? I'm sure there's some that will do most of the work for you...

Anyway the answer to your code above: your sort sub has many issues; the code doesn't even run on Perl 5.20:

  1. The slashes aren't escaped, but they're the pattern delimiter. I used m## -- Escapes fixed in OP too.
  2. You're not re-ordering the date components. The whole point is to short the year first; where do you think you're doing it? I sprintf() 'ed the date into a fixed YYYYMMDD number.
  3. You're only assigning the first element from the matches in $adate and $bdate , ie sorting only the month -- fixed using sprintf()
  4. You match fixed strings in date components, but day/month in your example has either one or two characters. I used \\d{1,2} for those instead.

Here's a fixed sub:

sub sortByDate {
    $a =~ m#(\d{1,2})/(\d{1,2})/(\d{4})#;
    my $adate = sprintf('%i%02i%02i', $3, $1, $2);
    $b =~ m#(\d{1,2})/(\d{1,2})/(\d{4})#;
    my $bdate = sprintf('%i%02i%02i', $3, $1, $2);
    return $adate <=> $bdate;
}

There is still no error checking, so running with strict/warning will return a lot of errors if you pass invalid data. You don't need the extra code if you validate the format first, but to prevent errors from badly formed dates you can also add some checking and use string cmp fallback:

sub sortByDate {
    my $adate = sprintf('%i%02i%02i', $3, $1, $2)
        if ($a =~ m#(\d{1,2})/(\d{1,2})/(\d{4})#);
    my $bdate = sprintf('%i%02i%02i', $3, $1, $2)
        if ($b =~ m#(\d{1,2})/(\d{1,2})/(\d{4})#);
    return $adate <=> $bdate if ($adate && $bdate);
    return $a cmp $b;
}

There could be multiple approaches to sort dates using Perl. I have summarized two approaches in the script.


Both the approach uses Schwartzian transform to sort it.

  1. Simply using split and then sort it based on year, month and day. Use it, if you are sure about the input format.
  2. Using Time::Piece to parse into Time::Piece object and then sort it. Use it, if you are not sure about input format and need validation before processing.

You can use anyone between them based on your preferences.

#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;

my @events = ( '11/17/1999',  '12/6/1999', '12/23/1999',
               '1/23/2000',  '1/13/2000',  '2/25/2000',
               '1/5/2000',  '3/18/2000',  '4/10/2000',
               '3/12/2000', '12/31/1999');


my @sorted_events = map { $_ -> [0] }
                    sort {
                          # year
                          $a -> [1] -> [2] <=> $b -> [1] -> [2] ||
                          # month
                          $a -> [1] -> [0] <=> $b -> [1] -> [0] ||
                          # day
                          $a -> [1] -> [1] <=> $b -> [1] -> [1]
                          }
                    map { [ $_, [ split /\// ] ] }
                    @events;

# you can also use Time::Piece
my @sorted_events_again = map { $_ -> [1] }
                          sort { $a -> [0] <=> $b -> [0] } 
                          map { [ Time::Piece -> strptime($_, "%m/%d/%Y"), $_ ] } 
                          @events;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM