How to rearrange dates of the month when they span across end of current month to beginning of next month

Viewed 96

I have to work on data which goes according to dates of the month. When I extract the dates from the filename I get a random arrangement of the dates, as below.

31 02 28 30 27 01 29 03 04

This is processing the data from 27th August to 4th September. I need the dates arranged sequentially from 27th to 4th. I have to process 9 dates at a time. This problem arises at the end of the month when I have this month's dates (large figures) and next month (small figures).So I sort the array which I call @fdaylist. I get

01 02 03 04 27 28 29 30 31.

To get what I want the script snippet below, does the job and gives

27 28 29 30 31 01 02 03 04

I am sure if this script can work all the time. It reproduces the sequence several time over, so I have to use the uniq() function. I will appreciate if I can get a suggestion of a better way of achieving my objective.

my $last_occ=lastidx { /0\d/ } @fdaylist;
if($last_occ > 1){
  foreach(@fdaylist){
    for(my $mm=$last_occ+1; $mm < @fdaylist; ++$mm){
        push (@fday, $fdaylist[$mm]);
    }
    for(my $nn=0; $nn <= $last_occ; ++$nn){
      push (@fday, $fdaylist[$nn]);
    }
  }
}
@fday = uniq(@fday);
2 Answers

I assume that the numbers must be consecutive, with one gap, otherwise it wouldn't be possible to decide which way some belong. I also assume that there always is a gap.† (Nope. While dates are indeed consecutive, they may straddle two months or lie within one, as clarified.)

One way: Go through the sorted array moving items from front to back until reaching a gap

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

# Dates are consecutive, either straddling two months or within a month
my @dates = (31, 02, 28, 30, 27, 01, 29, 03, 04);  # or can be 05..13 etc

@dates = sort { $a <=> $b } @dates;

if (consec_nums(\@dates)) {
    say "Consecutive numbers (dates in same month), nothing to do (@dates)";
}
else { 
    my $last_moved;
    while (1) { 
        push @dates, $last_moved = shift @dates;
        last if $dates[0] > $last_moved+1;
    }

    @dates = map { sprintf "%02d", $_ } @dates;   # for 01 02 etc
}
say "@dates";

# Returns true (1) if numbers are consecutive, like consecutive dates
# in a single month, false (undef) otherwise.  Assumes a sorted array
sub consec_nums {
    my ($ra) = @_; 
    my $prev = $ra->[0];
    for my $i (1..$#$ra) { 
        return if $ra->[$i] > $prev+1;
        $prev = $ra->[$i];
    }   
    return 1;
}  

Another way: Go over the sorted array to find the last index before the gap (after which numbers stop being consecutive); then splice-and-push

# same sorted @dates, same assumptions. same consec_nums() sub

if (consec_nums(\@dates)) {
    say "Consecutive numbers (dates in same month), nothing to do (@dates)";
}
else { 
    my $consec_idx;
    for my $i (0 .. $#dates) {
        $consec_idx = $i, last  
            if $dates[$i]+1 < $dates[$i+1];
    }
        
    push @dates, splice @dates, 0, $consec_idx+1;
}

say "@dates";

† For the mentioned assumptions:

  • If we could have 1 2 10 30 31 then should that be 10 30 31 1 2 or 30 31 1 2 10?

  • If there were no gap (all dates in one month), like 05 .. 13, then the procedures above would break. If such a case is possible then first test for it.

    It has been clarified in a comment that this is indeed possible, that all dates are inside one month. The answer above has then been amended with the test for such a case.

Please see if the following sample code performs operation you described in your post.

The code:

  • reads dates into a string
  • splits the sting and sorts dates into an array
  • find index of last day starting with 0\d
  • re-arranges the array with two array slices

Note: print out spits input, ordered and re-ordered data

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

while( <DATA> ) {
    chomp;
    say 'GOT: ' . $_;

    my @dates = sort split ' ', $_;
    say 'IN:  ' . join(' ',@dates);

    my $index = -1;
    /0\d/ && $index++ for @dates;

    @dates = (@dates[$index+1..$#dates],@dates[0..$index]);
    say 'OUT: ' . join(' ', @dates);
    say '-' x 45;
}

__DATA__
31 02 28 30 27 01 29 03 04
28 03 26 02 25 01 23 22 24

Output

GOT: 31 02 28 30 27 01 29 03 04
IN:  01 02 03 04 27 28 29 30 31
OUT: 27 28 29 30 31 01 02 03 04
---------------------------------------------
GOT: 28 03 26 02 25 01 23 22 24
IN:  01 02 03 22 23 24 25 26 28
OUT: 22 23 24 25 26 28 01 02 03
---------------------------------------------
Related