with Raku

This is my response to The Weekly Challenge #227.

You are given a year number in the range 1753 to 9999.

Write a script to find out how many dates in the year are Friday 13th, assume that the current Gregorian calendar applies.

Example:

File: friday-13th
Write a script to find out how many dates in the year are Friday 13th, assume that the current Gregorian calendar applies.

Example:

Input: $year = 2023
Output: 2
Since there are only 2 Friday 13th in the given year 2023 i.e. 13th Jan
and 13th Oct.

#! /usr/bin/env raku
unit sub MAIN (Int $year where 1753 <= $year <= 9999 = Date.today.year, # [1]
:v(:$verbose));
my $fridays = 0; # [2]
for 1 .. 12 -> $month # [3]
{
my $date = Date.new(year => $year, month => $month, day => 13); # [4]
my $is-friday = $date.day-of-week == 5; # [5]
$fridays++ if $is-friday; # [6]
say ": $date { $is-friday ?? " Friday" !! ""}" if $verbose;
}
say $fridays; # [7]

[1] Ensure that the user specified year is within the
limits, if given. If not, default to the current year (with
`Date.today.year`

).

See
docs.raku.org/type/Date
for information about the `Date`

class.

[2] The result will end up here.

[3] Iterate over the months.

[4] Create a `Date`

object for the 13th of each month.

[5] Check if it is indeed a Friday,

[6] and increase the counter if it is.

[7] Print the result (i.e. number of Friday 13ths in the given year).

Running it:

$ ./friday-13th
2

Looking good.

With verbose mode:

$ ./friday-13th -v
: 2023-01-13 Friday
: 2023-02-13
: 2023-03-13
: 2023-04-13
: 2023-05-13
: 2023-06-13
: 2023-07-13
: 2023-08-13
: 2023-09-13
: 2023-10-13 Friday
: 2023-11-13
: 2023-12-13
2

We can make it shorter, with `grep`

instead of the explicit `for`

loop:

#! /usr/bin/env raku
unit sub MAIN (Int $year where 1753 <= $year <= 9999 = Date.today.year);
say (1 .. 12).grep({Date.new(year => $year, month => $_,
day => 13).day-of-week == 5 }).elems;

Running it gives the expected result:

$ ./friday-13th-grep
2

Write a script to handle a 2-term arithmetic operation expressed in Roman numeral.

Example:

Example:

IV + V => IX
M - I => CMXCIX
X / II => V
XI * VI => LXVI
VII ** III => CCCXLIII
V - V => nulla (they knew about zero but didn't have a symbol)
V / II => non potest (they didn't do fractions)
MMM + M => non potest (they only went up to 3999)
V - X => non potest (they didn't do negative numbers)

This is very similar to the first part of Challenge 47: «Roman Calculator»; see Roman Gap with Raku for my take on it. The following code is a minor adaptation of code presented there, and I will only comment the changes.

We start off with a module with procedures converting to and from Roman numerals.

File: lib/Number/Roman.rakumodunit module Number::Roman;
our sub to-roman (Numeric $number is copy) is export(:to) # [1]
{
return "nulla" if $number == 0; # [2]
return "non potest" unless 0 < $number < 3999; # [3]
return "non potest" unless $number.Int == $number; # [4]
my $string = "";
while $number >= 1000 { $string ~= "M"; $number -= 1000; }
if $number >= 900 { $string ~= "CM"; $number -= 900; }
if $number >= 500 { $string ~= "D"; $number -= 500; }
if $number >= 400 { $string ~= "CD"; $number -= 400; }
while $number >= 100 { $string ~= "C"; $number -= 100; }
if $number >= 90 { $string ~= "XC"; $number -= 90; }
if $number >= 50 { $string ~= "L"; $number -= 50; }
if $number >= 40 { $string ~= "XL"; $number -= 40; }
while $number >= 10 { $string ~= "X"; $number -= 10; }
if $number >= 9 { $string ~= "IX"; $number -= 9; }
if $number >= 5 { $string ~= "V"; $number -= 5; }
if $number >= 4 { $string ~= "IV"; $number -= 4; }
while $number >= 1 { $string ~= "I"; $number -= 1; }
return $string;
}
my %value = (I => 1, V => 5, X => 10, L => 50, C => 100, D => 500, M => 1000);
my Set $valid-roman = %value.keys.Set;
my $current-value = Inf;
our sub from-roman (Str $roman) is export(:from)
{
my @digits = $roman.comb;
die "Non-Roman digit $_ detected." unless $valid-roman{$_} for @digits;
my $number = 0;
while @digits
{
my $current = @digits.shift;
if @digits.elems
{
if %value{@digits[0]} > %value{$current}
{
$number += %value{@digits.shift} - %value{$current};
next;
}
}
$number += %value{$current};
}
return to-roman($number) eq $roman
?? $number
!! die "Not a valid Roman Number: $roman";
}

[1] The type is now `Numeric`

instead of `Int`

. That makes
it possible to pass on rational numbers (as e.g. 2.5 in the `V / II`

example).

[2] Zeroes are illegal, return an error message.

[3] Negative vales, and values larger than 3999 are also illegal.

[4] Detect non-integer values.

The main program:

File: roman-maths#! /usr/bin/env raku
use lib "lib"; # [1]
use Number::Roman :to, :from;
unit sub MAIN (Str $first, Str $operator, Str $second);
my $f = from-roman($first);
my $s = from-roman($second);
given $operator
{
when '+' { say to-roman($f + $s) };
when '-' { say to-roman($f - $s) };
when 'x' { say to-roman($f * $s) }; # [2]
when '*' { say to-roman($f * $s) };
when 'xx' { say to-roman($f ** $s) }; # [3]
when '**' { say to-roman($f ** $s) }; # [3]
when '/' { say to-roman($f / $s) };
default { die "unknown operator"; } # [4]
}

[1] The module is located in this directory.

[2] The `*`

operation has this alias, so that we can drop the quotes
(as the shell interferes with unquoted `*`

).

[3] This operation in new in this version of the program.

[4]
Error catching with `default`

on the
`given`

/`when`

structure was missing in the original
program. My bad.

See
docs.raku.org/language/control#index-entry-switch_(given)
for more information about `given`

/`when`

.

See
docs.raku.org/language/control#default_and_when
for more information about `default`

.

Running it:

$ ./roman-maths IV + V
IX
./roman-maths M - I
CMXCIX
$ ./roman-maths X / II
V
$ ./roman-maths XI x VI
LXVI
$ ./roman-maths VII xx III
CCCXLIII
$ ./roman-maths V - V
nulla
$ ./roman-maths V / II
non potest
$ ./roman-maths MMM + M
non potest
$ ./roman-maths V - X
non potest

Looking good.

And that's it.