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 Musical — Seize 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