perl sort multiple arryrefs of date time strings

175 Views Asked by At

I want to sort an arrayref %results (Time-strings, from old to new), it has multiple keys but I just posted one key to show how it looks like:

'Ende Monatswechsel P-Konten' => [
                                         '17.02.2018 05:17:39',
                                         '14.02.2018 04:28:11',
                                         '23.02.2018 03:17:17',
                                         '22.02.2018 03:39:20',
                                  ]

I am expecting:

    'Ende Monatswechsel P-Konten' => [
                                         '14.02.2018 04:28:11',
                                         '17.02.2018 05:17:39',
                                         '22.02.2018 03:39:20',
                                         '23.02.2018 03:17:17',
                                  ]

Does any know how to do this? I tried:

my $columns = map [ $_, sort{$a <=> $b} @{ $results{$_} } ], keys %results;

but it doesn't work. Thanks in advance.

My code looks like this:

while(my $line=<F>) {
    #- Info: 19.02.2018 00:01:01 --- Start Tageswechsel-CoBa ---
    #- Info: 27.11.2018 04:16:42 --- Ende Tageswechsel-CoBa ---
            if ($line=~ /(\d\d\.\d\d\.\d\d\d\d \d\d:\d\d:\d\d) --- (.+? Tageswechsel-CoBa) -.*\s*$/)
            {
                    ($timestamp, $action) = ($1,$2);
            }
            if ( !defined $filter{$action}{$timestamp} ) {
                    push @{$results{$action}}, $timestamp;
                    $filter{$action}{$timestamp} = 1;
            }
}

print Dumper(\%results) outputs:

'Start Tageswechsel-CoBa' => [
                                '17.02.2018 05:12:13',
                                '20.02.2018 04:23:16',
                                '22.02.2018 03:12:46',
                                '23.02.2018 03:34:28',
                                '27.02.2018 03:41:25',
                                '02.03.2018 03:32:26',
            ],
'Ende Tageswechsel-CoBa' => [
                                    '17.02.2018 05:20:01',
                                    '19.02.2018 06:01:02',
                                    '20.02.2018 04:29:44',
                                    '22.02.2018 03:19:04',
                                    '23.02.2018 03:40:52',
                                    '26.02.2018 06:01:26',
            ]
            };
3

There are 3 best solutions below

7
On

Something like this would work:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my $data = [
  '17.02.2018 05:17:39',
  '14.02.2018 04:28:11',
  '23.02.2018 03:17:17',
  '22.02.2018 03:39:20',
];

my @sorted = sort {
  my @a = split /[\. ]/, $a;
  my @b = split /[\. ]/, $b;
  return (
    $a[2] <=> $b[2] or  # year
    $a[1] <=> $b[1] or  # month
    $a[0] <=> $b[0] or  # day of month
    $a[3] cmp $b[3]     # time
  );
} @$data;

say Dumper @sorted;

I'm splitting each value into chunks and then sorting them from largest chunk to smallest. Note that as the time is a string, not a number I use cmp instead of <=>.

This is slightly inefficient, as I'm re-splitting each data item several times. If that's a problem, then you could look at something like a Schwartzian Transform.

But the best solution to this would be to get a sortable timestamp in the first place. If your dates were YYYY.MM.DD HH:MM:SS, then you could just do a simple string sort.

Update: My output is

$ perl sortdate
$VAR1 = '14.02.2018 04:28:11';
$VAR2 = '17.02.2018 05:17:39';
$VAR3 = '22.02.2018 03:39:20';
$VAR4 = '23.02.2018 03:17:17';

Update 2: I've edited my code to make it more like your example. Hope this helps.

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my %results = (
  'Ende Monatswechsel P-Konten' => [
    '17.02.2018 05:17:39',
    '14.02.2018 04:28:11',
    '23.02.2018 03:17:17',
    '22.02.2018 03:39:20',
  ]
);

foreach my $k (keys %results) {
  my @sorted = sort {
    my @a = split /[\. ]/, $a;
    my @b = split /[\. ]/, $b;
    return (
      $a[2] <=> $b[2] or  # year
      $a[1] <=> $b[1] or  # month
      $a[0] <=> $b[0] or  # day of month
      $a[3] <=> $b[3]     # time
    );
  } @{ $results{$k} };

  $results{$k} = \@sorted;
}

say Dumper \%results;

And the output...

$VAR1 = {
          'Ende Monatswechsel P-Konten' => [
                                             '14.02.2018 04:28:11',
                                             '17.02.2018 05:17:39',
                                             '22.02.2018 03:39:20',
                                             '23.02.2018 03:17:17'
                                           ]
        };
8
On

Splitting the strings and comparing the parts is appropriate for sorting many types of "multipart" values, however since you are dealing with datetimes, you can use the core module Time::Piece to turn the strings into datetime objects which can be compared using the <=> operator.

Time::Piece provides the strptime method, which parses a date string into a Time::Piece object using a format string. Time::Piece objects can be compared using numerical comparison operators.

use v5.10;
use strict
use warnings;
use Time::Piece;

my @vals = (
    '17.02.2018 05:17:39',
    '14.02.2018 04:28:11',
    '23.02.2018 03:17:17',
    '22.02.2018 03:39:20',
);

say for sort {dt($a) <=> dt($b)} @vals;

###

sub dt {
    my $str = shift;
    return Time::Piece->strptime($str,'%e.%m.%Y %H:%M:%S') 
}
0
On

I actually used Dave's approach now (since I don't have the module Time::Piece installed) slightly different but it works now, not sure though about the efficency:

my @array;
my @sorted;
my %aref_n;

for my $key ( keys %results ) {
    for my $i (0..$#{ $results{$key} }) {
            push @array, $results{$key}[$i];
    }

    @sorted = sort {
            my @a = split /[\. ]/, $a;
            my @b = split /[\. ]/, $b;
            return (
                    $a[2] <=> $b[2] or
                    $a[1] <=> $b[1] or
                    $a[0] <=> $b[0] or
                    $a[3] cmp $b[3]
                    );
            } @array;

    $aref_n{$key} = [ @sorted ];
    @array=();

}