This is my response to The Weekly Challenge #237.
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
#! /usr/bin/env raku
unit sub MAIN (Int $year, # [1]
Int $month, # [1]
Int $weekday-of-month, # [1]
Int $day-of-week, # [1]
:v(:$verbose));
my @days = "", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
"Saturday", "Sunday"; # [2]
my $d = Date.new($year, $month, 1); # [3]
say ":The first day in the month: $d" if $verbose;
my $dow = $d.day-of-week; # [4]
$d = $d.later(days => abs($day-of-week - $dow)) if $dow != $day-of-week;
# [5]
say ":The first @days[$day-of-week] in the month: $d" if $verbose;
$d = $d.later(weeks => $weekday-of-month -1); # [6]
say ":The { $weekday-of-month }{ $weekday-of-month == 1 ?? "st" !! "nd" } \
@days[$day-of-week] in the month: $d" if $verbose;
say $d.month == $month # [7]
?? $d.day
!! 0;
[1] Separate integer arguments for the values.
[2] Mapping to day names, used by verbose output only.
[3] Generate a Date
object for the
first day in the given month and year.
See
docs.raku.org/type/Date
for information about the Date
class.
[4] Get the day of week (1=Monday.. 7=Sunday) for the date in [3].
[5] Are we at the required day of week? If not, add the missing number of days to get there.
[6] We are now at the first of those days in the month. Add as many weeks to get to the target date.
[7] Print the day (day in month) if we are in the correct month, and 0 otherwise.
Running it:
$ ./seize-the-day 2024 4 3 2
16
$ ./seize-the-day -v 2025 10 2 4
9
Looking good.
With verbose mode:
$ ./seize-the-day -v 2024 4 3 2
:The first day in the month: 2024-04-01
:The first Tuesday in the month: 2024-04-02
:The 3nd Tuesday in the month: 2024-04-16
16
$ ./seize-the-day -v 2025 10 2 4
:The first day in the month: 2025-10-01
:The first Thursday in the month: 2025-10-02
:The 2nd Thursday in the month: 2025-10-09
9
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]
Permute?
Permute indeed. Off we go with permutations...
File: maximise-greatness-permutations
#! /usr/bin/env raku
unit sub MAIN (*@nums where all(@nums) ~~ Int && @nums.elems > 0, # [1]
:v(:$verbose));
my $max = 0; # [2]
for @nums.permutations -> @permutation # [3]
{
my $sum = [+] (@nums Z @permutation).map({ + ($_[0] < $_[1] ) }); # [4]
if $sum > $max # [5]
{
say ":Permutation: @permutation[] -> $sum [max]" if $verbose;
$max = $sum; # [5a]
}
elsif $verbose
{
say ":Permutation: @permutation[] -> $sum";
}
}
say $max; # [6]
[1] At least one element, and they must be integers. The examples use positive integers only, but this is not a requirement.
[2] The higest possible score, so far.
[3] Iterate over all the permutations (with
permutations
).
See
docs.raku.org/routine/permutations for more information about permutations
.
[4] Merge the original list with the permutation (with the Z
zipper operator). The result is a list of Pair objects; with one value from each list in
each Pair. Then we use map
to compare the two values in the Pairs. The
result of that is a Boolean value, so we use the +
prefix to coerce it to 0
or 1. The leading [+]
adds all those values together, giving the greatness.
See
docs.raku.org/routine/Z
for more information about the infix zip operator Z
.
[5] Do we have a new highscore? If so, take note [5a].
[6] Print the result.
Running it:
$ ./maximise-greatness-permutations 1 3 5 2 1 3 1
4
$ ./maximise-greatness-permutations -v 1 2 3 4
3
The first example gives 5040 rows of verbose output, so is not shown here. The second example is much easier on the reader:
$ ./maximise-greatness-permutations -v 1 2 3 4
:Permutation: 1 2 3 4 -> 0
:Permutation: 1 2 4 3 -> 1 [max]
:Permutation: 1 3 2 4 -> 1
:Permutation: 1 3 4 2 -> 2 [max]
:Permutation: 1 4 2 3 -> 1
:Permutation: 1 4 3 2 -> 1
:Permutation: 2 1 3 4 -> 1
:Permutation: 2 1 4 3 -> 2
:Permutation: 2 3 1 4 -> 2
:Permutation: 2 3 4 1 -> 3 [max]
:Permutation: 2 4 1 3 -> 2
:Permutation: 2 4 3 1 -> 2
:Permutation: 3 1 2 4 -> 1
:Permutation: 3 1 4 2 -> 2
:Permutation: 3 2 1 4 -> 1
:Permutation: 3 2 4 1 -> 2
:Permutation: 3 4 1 2 -> 2
:Permutation: 3 4 2 1 -> 2
:Permutation: 4 1 2 3 -> 1
:Permutation: 4 1 3 2 -> 1
:Permutation: 4 2 1 3 -> 1
:Permutation: 4 2 3 1 -> 1
:Permutation: 4 3 1 2 -> 2
:Permutation: 4 3 2 1 -> 2
3
Permutations got us there, in a roundabout way, but we can do this much more efficiently.
The trick is to inspect each number in the input with a doctored version; not a full set of permutations. For this to work, we have to sort the input values.
See the description below the illustration.
[0] The input integers, as given on the command line.
[1] The order is irrelevant for the task, but having them sorted makes our job much easier. The highest value is first.
[2-8] We iterate over the sorted values, and compare them with the first value in the sorted array. If the iterated value is lower, then we have satisfied the condition and increases the counter - and removes the first value (as it has been used to take out the iterated value). If not, then we remove the last value in the array, as that one has the least possibility of being useful to take out future values. The counter is not increased.
[2,4,8] The condition is not satisfied, so we take out the last value in the array.
[3,5,6,7] The condition is satisfied, so we take out the first value in the array - and increases the counter.
Verbose mode sort of says the same thing, albeit much terser:
$ ./maximise-greatness -v 1 3 5 2 1 3 1
:Num: 5 >= Perm: 1
:Num: 3 < Perm: 5 [+]
:Num: 3 >= Perm: 1
:Num: 2 < Perm: 3 [+]
:Num: 1 < Perm: 3 [+]
:Num: 1 < Perm: 2 [+]
:Num: 1 >= Perm: 1
4
$ ./maximise-greatness -v 1 2 3 4
:Num: 4 >= Perm: 1
:Num: 3 < Perm: 4 [+]
:Num: 2 < Perm: 3 [+]
:Num: 1 < Perm: 2 [+]
3
File: maximise-greatness
#! /usr/bin/env raku
unit sub MAIN (*@nums where all(@nums) ~~ Int && @nums.elems > 0,
:v(:$verbose));
my $count = 0; # [1]
my @sorted = @nums.sort.reverse; # [2]
for @sorted.clone -> $num # [3]
{
if $num < @sorted[0] # [4]
{
$count++; # [4a]
say ":Num: $num < Perm: @sorted[*-1] [+]" if $verbose;
@sorted.shift; # [4b]
}
else # [5]
{
say ":Num: $num >= Perm: @sorted[0]" if $verbose;
@sorted.pop; # [5a]
}
}
say $count; # [6]
[1] The result, we add (1 or not) to this as we go along the single pass (in [3]).
[2] We need the values sorted, with the highest first.
[3] We have to iterate over a copy (with
clone
), as we change the original (in [4b and 5a]), and
that will screw up the iteration.
See
docs.raku.org/routine/clone
for more information about clone
.
[4] Less than the highest remaining number? If so, add to the count [4a] and remove the highest remaining number [4b].
[5] If not, remove the lowest remaining number [5a].
[6] Print the result.
Running it gave the expected result, see the verbose output above.
And that's it.