Perl Weekly Challenge: Minute by minute, that’s how you win it!

Ok, it’s time for another Perl Weekly Challenge, but first a note about the blog title: when I first saw the name of the first task, my brain started singing the big number from Disney’s Newsies: The MusicalSeize The Day. Yes, I’m a Perl geek, but I’m also a theater geek.

So, onward with Perl Weekly Challenge 237!

Task 1: Seize The Day

Given a year, a month, a weekday of month, and a day of week (1 (Mon) .. 7 (Sun)), print the day.

Example 1

Input: Year = 2024, Month = 4, Weekday of month = 3, day of week = 2
Output: 16

The 3rd Tue of Apr 2024 is the 16th

Example 2

Input: Year = 2025, Month = 10, Weekday of month = 2, day of week = 4
Output: 9

The 2nd Thu of Oct 2025 is the 9th

Example 3

Input: Year = 2026, Month = 8, Weekday of month = 5, day of week = 3
Output: 0

There isn't a 5th Wed in Aug 2026

This reminds me a lot of challenge 227, which also involved manipulating dates: in that case, counting up the number of Friday the 13ths in a given year. For that challenge, I started at the first of the year, then found the first Friday, then rolled forward by a week and counted the number of times the day of the month was the 13th. This time, I’m starting at the first of the month that’s specified, finding the first occurrence of the specified day of the week, and then rolling forward by a week until I reach the desired weekday of the month or I roll off the end of the month.

Caveats:

  • The format of the examples is using a 0-Sunday-indexed day of the week (0 = Sun, 2 = Tue, 3 = Wed, 4 = Thu), but Time::Piece‘s wday method is 1-Sunday-indexed (1 = Sun, 3 = Tue, 4 = Wed, 5 = Thu)
  • If I want to print out the descriptive text, I probably want to use the ordinal method in Lingua::EN::Inflexion::Noun (I used the Lingua::EN::Inflexion module back in challenge 230).
# let's use the core modules for date manipulation
use Time::Piece;
use Time::Seconds qw( ONE_DAY );
use Lingua::EN::Inflexion qw( noun );

sub seizeTheDay {
  my %params = @_;
  # build the first day of the specified month
  my $start = $params{year} . '-' . $params{month} . '-01';
  # create an object for Jan 01 of the given year
  my $t = Time::Piece->strptime($start, "%Y-%m-%d")
                     ->truncate(to => 'day');
  # in Time::Piece->wday, 1 = Sun, 2 = Mon, 3 = Tue, but our
  # input is 0 = Sun, 1 = Mon, 2 = Tue, so adjust our input
  $params{day_of_week}++;

  # find the FIRST instance of the desired day of the week
  while ( $t->wday != $params{day_of_week} ) {
    $t += ONE_DAY; # add 1 day
  }

  # take note of some values that won't change
  # for our description
  my $year  = $t->year;
  my $month = $t->month;
  my $dow   = $t->wdayname;
  my $count = 1;

  my $ord_weekday_of_month = 
    noun($params{weekday_of_month})->ordinal(0);

  # now roll forward through the month until the desired
  # weekday of the month
  while (
    # we're still in the desired month
    $t->mon == $params{month}
    &&
    # we haven't reached the desired weekday of the month
    $count != $params{weekday_of_month}
  ) {
    # add a week to the date
    $t += ONE_DAY * 7;
    # add to the weekday of the month count
    $count++;
  }

  # if we rolled out of the month, return an error condition
  if ($t->mon != $params{month}) {
    return 0, "There isn't a $ord_weekday_of_month $dow "
            . "in $month $year";
  }
  else {
    # take note of what the day of the month is
    my $day = $t->day_of_month;
    my $ord_day_of_month = noun($day)->ordinal(0);
    return $day, "The $ord_weekday_of_month $dow "
               . "of $month $year is the $ord_day_of_month";
  }
}

View the entire Perl script for this task on GitHub.


In Raku, the built-in Date class is easier to deal with, and the output of the .day-of-week method matches the input we’re getting for the day of week, but…

  • There’s no built-in facility in Date to produce the English names for months and weekdays, so I loaded Tom Browder’s Date::Names module.
  • I needed a way to get ordinals from numbers, so I loaded Steve Schulze’s Lingua::EN::Numbers.
use Date::Names;
use Lingua::EN::Numbers; # for ordinal-digit()

sub seizeTheDay(
  Int :$year,
  Int :$month,
  Int :$weekday_of_month,
  Int :$day_of_week
) {
  # object for the first day of the specified month
  my $t = Date.new($year, $month, 1);

  # in Date.day-of-week, 0 = Sun, 1 = Mon, 2 = Tue,
  # which matches our input, so no adjustment is needed

  # find the FIRST instance of the desired day of the week
  while ( $t.day-of-week != $day_of_week ) {
    $t++; # add 1 day
  }

  # instantiate a Date::Names object
  my $dn = Date::Names.new;

  # take note of some values that won't change
  # for our description
  my $month_name = $dn.mon($t.month);
  my $dow        = $dn.dow($t.day-of-week);
  my $count      = 1;

  my $ord_weekday_of_month = ordinal-digit($weekday_of_month);

  # now roll forward through the month until the desired
  # weekday of the month
  while (
    # we're still in the desired month
    $t.month == $month
    &&
    # we haven't reached the desired weekday of the month
    $count < $weekday_of_month
  ) {
    # add a week to the date
    $t += 7;
    # add to the weekday of the month count
    $count++;
  }

  # if we rolled out of the month, return an error condition
  if ($t.month != $month) {
    return 0, "There isn't a $ord_weekday_of_month $dow "
            ~ "in $month $year";
  }
  else {
    # take note of what the day of the month is
    my $day = $t.day;
    my $ord_day_of_month = ordinal-digit($day);
    return $day, "The $ord_weekday_of_month $dow "
               ~ "of $month $year is the $ord_day_of_month";
  }
}

View the entire Raku script for this task in GitHub.


Python has a really robust datetime module, so of course I was going to use that, but I didn’t find anything in the standard library that generated ordinal numbers, so I went ahead and installed Savoir-faire Linux’s num2words module.

from datetime  import date, timedelta
from num2words import num2words

def seizeTheDay(year, month, weekday_of_month, day_of_week):
    """
    Function to determine, given a year, month, weekday of
    month and day of week, whether such a date exists and,
    if so, what day of the month it is.
    """
    # object for the first day of the specified month
    t = date(year, month, 1)

    # datetime.date.isoweekday returns 1 = Mon, 2 = Tue, etc.,
    # which matches our input, so no adjustment is needed

    # find the FIRST instance of the desired day of the week
    while ( t.isoweekday() != day_of_week ):
        t += timedelta(days = 1) # add 1 day

    # take note of some values that won't change
    # for our description
    month_name = t.strftime('%b')
    dow        = t.strftime('%a')
    count      = 1
  
    ord_weekday_of_month = num2words(
        weekday_of_month, to="ordinal_num"
    )

    # now roll forward through the month until the desired
    # weekday of the month
    while (
      # we're still in the desired month
      t.month == month
      and
      # we haven't reached the desired weekday of the month
      count < weekday_of_month
    ):
        # add a week to the date
        t += timedelta(days = 7)
        # add to the weekday of the month count
        count += 1

    # if we rolled out of the month, return an error condition
    if (t.month != month):
        return(
          0,
          f"There isn't a {ord_weekday_of_month} {dow} " +
          f"in {month_name} {year}"
        )
    else:
        # take note of what the day of the month is
        day = t.day
        ord_day_of_month = num2words(day, to="ordinal_num")
        return(
            day,
            f"The {ord_weekday_of_month} {dow} " +
            f"of {month_name} {year} is the {ord_day_of_month}"
        )

View the entire Python script in GitHub.


For the Java implementation, I decided to make seizeTheDay its own class, because I could then return an object that had attributes with the day and description in it. I had to search to learn how to do date manipulation, but once I found examples, it was pretty easy to grok. I couldn’t find a standard library to turn my numbers into ordinals, so I rolled my own.

I also learned that String.format() can accept C printf()-style format strings, not the weird positional ones I’d been using. That was a welcome discovery,

import java.util.Calendar;  
import java.text.SimpleDateFormat;

class seizeTheDay {
  public int    day;
  public String description;

  public seizeTheDay(
    int year,
    int month,
    int weekday_of_month,
    int day_of_week
  ) {
    // object for the first day of the specified month
    Calendar t = Calendar.getInstance();
    t.set(year, month - 1, 1);

    // find the FIRST instance of the desired day of the week
    while (t.get(Calendar.DAY_OF_WEEK) != day_of_week+1) {
      t.add(Calendar.DATE, 1);
    }

    String month_str =
      new SimpleDateFormat("MMM").format(t.getTime());
    String dow_str =
      new SimpleDateFormat("EEE").format(t.getTime());
    int count = 1;

    // now roll forward through the month until the desired
    // weekday of the month
    while (
      // we're still in the desired month
      t.get(Calendar.MONTH) == month - 1
      && 
      // we haven't reached the desired weekday of the month
      count < weekday_of_month
    ) {
      t.add(Calendar.DATE, 7);
      count++;
    }
    if (t.get(Calendar.MONTH) != month - 1) {
      this.day = 0;
      this.description = String.format(
        "There isn't a %s %s in %s %d",
        this.ord_suffix(weekday_of_month),
        dow_str,
        month_str,
        year
      );
    }
    else {
      this.day = t.get(Calendar.DATE);
      this.description = String.format(
        "The %s %s of %s %d is the %s",
        this.ord_suffix(weekday_of_month),
        dow_str,
        month_str,
        year,
        this.ord_suffix(this.day)
      );
    }
  }

  private String ord_suffix(int num) {
    // quick function to add an ordinal suffix
    // to a number
    if (num == 11 || num == 12 | num == 13) {
      return num + "th";
    }
    else {
      switch (num % 10) {
        case 1:  return num + "st";
        case 2:  return num + "nd";
        case 3:  return num + "rd";
        default: return num + "th";
      }
    }
  }
}

View the entire Java file in GitHub.


Task 2: Maximise Greatness

You are given an array of integers.

Write a script to permute the given array such that you get the maximum possible greatness.

To determine greatness, nums[i] < perm[i] where 0 <= i < nums.length

Example 1

Input: @nums = (1, 3, 5, 2, 1, 3, 1)
Output: 4

One possible permutation: (2, 5, 1, 3, 3, 1, 1) which returns 4 greatness as below:
nums[0] < perm[0]
nums[1] < perm[1]
nums[3] < perm[3]
nums[4] < perm[4]

Example 2

Input: @ints = (1, 2, 3, 4)
Output: 3

One possible permutation: (2, 3, 4, 1) which returns 3 greatness as below:
nums[0] < perm[0]
nums[1] < perm[1]
nums[2] < perm[2]

This problem looks like there are multiple ways to solve it. The simplest way would be to just make all the possible permutations of the number array and count up the “greatness” of each. But for the first example with an array of 7 items, that means generating 5040 (7!) permutations and testing each of them. There has to be a better way.

I think there is: count up how many of each number we have, and then build a permutation item by item where either the item is the smallest remaining number larger than the current item, or the smallest remaining number. In fact, when we look at the example permutations, that’s exactly how they’re structured…

In example 1, we have the list 1, 3, 5, 2, 1, 3, 1, which has three 1s, one 2, two 3s, and one 5. So when building the permutation, we pair up one of the 2s with the first 1, the 5 with the first 3, then because there’s nothing greater than 5, we use one of the 1s, then one of the 3s with the 2, the remaining 3 with the next 1, and then we have to use the remaining 1s for the rest of the list.

In example 2, we have the list 1, 2, 3, 4, which has one each of 1, 2, 3, 4, and as we build the greatest permutation, each time we pair up the smallest possible number that’s greater than the current item: 2, 3, 4, and then finally the 1, because nothing in the list was greater than 4.

So, now to implement this in Perl. My first crack looked like this:

sub greatness {
  # determine the "greatness" of a permutation
  # relative to the original array; accepts two
  # array references to do the comparison
  my ($nums, $perm) = @_;
  my $greatness = 0;
  foreach my $i ( 0 .. $#$nums ) {
    $greatness++ if $nums->[$i] < $perm->[$i];
  }
  return $greatness;
}

sub removeNumFromCount {
  # since we do this in two locations below,
  # make this a subroutine we can call;
  # accept a reference to the count hash and
  # the number being removed
  my ($num_count, $num) = @_;

  # decrement the count of that number available
  $num_count->{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash
  delete $num_count->{$num}
    if $num_count->{$num} == 0;
}

sub greatestPermutation {
  my @nums = @_;
  # first, count up how many of each num we have
  my %num_count;
  foreach my $num ( @nums ) {
    $num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  NUM: foreach my $num ( @nums ) {
    my @available = sort keys %num_count;
    my $smallest_available = $available[0];
    foreach my $avail ( @available ) {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        push @perm, $avail;

        removeNumFromCount(\%num_count, $avail);

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(\%num_count, $smallest_available);
  }

  return @perm;
}

But I was getting bothered by repeating sort keys %num_count the once per item in the input array. Sorting is a somewhat expensive operation, and really, if we’ve sorted the list of available numbers once, we don’t need to sort it again—we just need to remove numbers that aren’t available anymore, and that can be accomplished with a simple scan of the array. So I modified removeNumFromCount to accept a second reference, this time to the array I’m storing the result of sort keys %num_count in:

sub removeNumFromCount {
  # since we do this in two locations below,
  # make this a subroutine we can call;
  # accept references to the count hash and
  # the list of available numbers, and the
  # number being removed
  my ($num_count, $available, $num) = @_;

  # decrement the count of that number available
  $num_count->{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash and the @available array
  if ( $num_count->{$num} == 0 ) {
    # remove key from the hash
    delete $num_count->{$num};
    # filter array to not include $num
    @$available = grep { $_ != $num } @$available;
  }
}

sub greatestPermutation {
  my @nums = @_;
  # first, count up how many of each num we have
  my %num_count;
  foreach my $num ( @nums ) {
    $num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  my @available = sort keys %num_count; # do the sort once
  NUM: foreach my $num ( @nums ) {
    my $smallest_available = $available[0];
    foreach my $avail ( @available ) {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        push @perm, $avail;

        removeNumFromCount(
          \%num_count, \@available, $avail
        );

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(
      \%num_count, \@available, $smallest_available
    );
  }

  return @perm;
}

View the entire Perl script for this task on GitHub.


The Raku version is pretty much exactly the same, except for the syntactical differences between Perl and Raku:

sub greatness(@nums, @perm) {
  # determine the "greatness" of a permutation
  # relative to the original array; accepts two
  # arrays to do the comparison
  my $greatness = 0;
  for 0 .. @nums.elems - 1 -> $i {
    $greatness++ if @nums[$i] < @perm[$i];
  }
  return $greatness;
}

sub removeNumFromCount(%num_count, @available, $num) {
  # since we do this in two locations below,
  # make this a subroutine we can call; accept
  # the count hash and the list of available
  # numbers, and the number being removed

  # decrement the count of that number available
  %num_count{$num}--;

  # if there are no more of that number, remove it
  # from the %num_count hash and the @available array
  if ( %num_count{$num} == 0 ) {
    # remove key from the hash
    %num_count{$num}:delete;
    # filter array to not include $num
    @available = @available.grep( { $_ != $num } );
  }
}

sub greatestPermutation(@nums) {
  # first, count up how many of each num we have
  my %num_count;
  for @nums -> $num {
    %num_count{$num}++;
  }

  # now, build a permutation that maximizes "greatness"
  my @perm;
  my @available = %num_count.keys().sort(); # do the sort once
  NUM: for @nums -> $num {
    my $smallest_available = @available[0];
    for @available -> $avail {
      if ( $avail > $num ) {
        # push the available number onto the permutation
        @perm.push($avail);

        removeNumFromCount(
          %num_count, @available, $avail
        );

        # go to the next input number
        next NUM;
      }
    }
    # we didn't find an available number larger than $num,
    # so let's put the smallest available number on @perm
    push @perm, $smallest_available;

    removeNumFromCount(
      %num_count, @available, $smallest_available
    );
  }

  return @perm;
}

View the entire Raku script for this task in GitHub.


When writing the Python version for this, it occurred to me that I don’t need to remove values from num_count when their count becomes 0 anymore: because I’m just removing values from available when their count drops to 0 instead of re-populating available by sorting the keys of num_count, I don’t care if there are keys in num_count with a 0 count anymore. I’m getting my next possible values for the permutation from available. I’ve gone back and made this change to the Perl and Raku versions I checked in, but I’m keeping how I originally wrote about them above.

def greatness(nums, perm):
    """
    Function to enumerate the greatness of
    the list perm relative to the list nums
    """
    greatness_num = 0
    for i in range(0, len(nums) - 1):
        if nums[i] < perm[i]:
            greatness_num += 1
    return greatness_num

def greatestPermutation(nums):
    """
    Function to generate a permutation of the list nums
    which has the largest relative "greatness" to nums
    """

    # first, count up how many of each num we have
    num_count = {}
    for num in nums:
        num_count[num] = num_count.get(num, 0) + 1

    # now, build a permutation that maximizes "greatness"
    perm = []
    available = sorted(num_count.keys()) # only sort once
    for num in nums:
        # default to the smallest available number
        num_to_add = available[0]

        # but now look for the smallest available number
        # that's GREATER than the current number
        for avail in available:
            if avail > num:
                num_to_add = avail
                break

        # add num_to_add to the permutation
        perm.append(num_to_add)

        # decrement its count in num_count
        num_count[num_to_add] -= 1

        # if there are no more, remove it from available
        if num_count[num_to_add] == 0:
            available = [
                x for x in available if x != num_to_add
            ]

    return perm

View the entire Python script in GitHub.


This Java solution was trickier because I wound up using a lot of three-term loops which I didn’t always get right.

  public static int greatness(int[] nums, int[] perm) {
    // determine the "greatness" of a permutation
    // relative to the original array; accepts two
    // arrays to do the comparison
    int greatness_num = 0;
    for (int i = 0; i < nums.length; i++) {
      if (nums[i] < perm[i]) {
        greatness_num++;
      }
    }
    return greatness_num;
  }

  public static int[] greatestPermutation(int[] nums) {
    // first, count up how many of each num we have
    HashMap<Integer, Integer> num_count =
      new HashMap<Integer, Integer>();
    for (int i = 0; i < nums.length; i++) {
      num_count.put(
        nums[i],
        num_count.getOrDefault(nums[i], 0) + 1
      );
    }

    // make a list of the available numbers
    // to put in a permutation
    List<Integer> available =
      new ArrayList<>(num_count.keySet());
    Collections.sort(available);

    // now, build a permutation that maximizes "greatness"
    List<Integer> perm = new ArrayList<>();
    for (Integer num : nums) {
      // default to the smallest available number
      int num_to_add = available.get(0);
      for (int i = 0; i < available.size(); i++) {
        int this_num = available.get(i);
        if (num < this_num) {
          num_to_add = this_num;
          break;
        }
      }
      perm.add(num_to_add);

      // decrement the count of that number available
      num_count.put(
        num_to_add,
        num_count.get(num_to_add) - 1
      );
  
      // if there are no more of that number, remove it
      // from available list
      if ( num_count.get(num_to_add) == 0 ) {
        // filter array to not include $num
        int size = available.size();
        for (int i = 1; i < size; i++) {
          int this_num = available.get(i);
          if (num_to_add == this_num) {
            available.remove(i);
            break;
          }
        }
      }
    }

    // because we built the permutations in a List,
    // convert the list to an int array for return
    int[] perm_return = new int[perm.size()];
    for (int i = 0; i < perm.size(); i++) {
      perm_return[i] = perm.get(i);
    }
    return perm_return;
  }

View the entire Java file in GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-237/packy-anderson