with Raku (and Perl)

This is my response to the Perl Weekly Challenge #138.

You are given a year,

Write a script to calculate the total number of workdays in the given year.

For the task, we consider, Monday - Friday as workdays.

Example 1:

`$year`

in 4-digits form.
Write a script to calculate the total number of workdays in the given year.

For the task, we consider, Monday - Friday as workdays.

Example 1:

Input: $year = 2021
Output: 261

Example 2:
Input: $year = 2020
Output: 262

The calender used is the Gregorian calendar, which replaced the Julian calendar from as early as 1582 - depending on country. Dates before the adoption are left as they are. This leads to a fine mess, which I have ignored. The program assumes a Gregorian calender all the way back to year 0 (or 0000, as you would have to specify it).

File: workdays#! /usr/bin/env raku
unit sub MAIN (Int $year where $year.chars == 4); # [1]
my $d = Date.new("$year-01-01"); # [2]
my $first-day = $d.day-of-week; # [3]
my @workdaysinyear = # [4]
(
0, # dummy
261, # Monday - Monday
261, # Tuesday - Tuesday
261, # Wednesday - wednesday
261, # Thursday - Thursday
261, # Friday - Friday
260, # Saturday - Saturday
259, # Sunday - Sunday
);
my @add-leap = # [5]
(
0, # Dummy
1, # Mo -> Tu
1, # Tu -> We
1, # We -> Th
1, # Th -> Fr
0, # Fr -> Sa
0, # Sa -> Su
1, # Su -> Ma
);
my $count = @workdaysinyear[$first-day] # [6]
+ ($d.is-leap-year ?? @add-leap[$first-day] !! 0);
say $count;

[1] Ensure a four digit number as input. Leading zeroes are permitted.

[2] Start with the first day of the specified year,

[3] and get which day of the week it is. (Monday is 1, up to Sunday which is 7.)

[4] The number of workdays in a given year, based on 365 days/year (i.e. ignoring leap years). 7 days * 52 weeks = 364 days. So we have to add on a single day to get to 365. The numer og working days is 5 * 52 = 260 + the extra day, if they are all workingdays. They are not. The table has the values. The first day in the comment is the first day of the year, and the second is the last.

[5] If the year is a leap year, does the extra day (number 365) land on a working day?

[6] Get the number of days + any leap year working day.

Running it:

$ ./workdays 2021
261
$ ./workdays 2020
262

#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use DateTime;
my $year = shift(@ARGV) // "";
die "Please specify a 4 digit year" unless $year =~ /^\d\d\d\d$/;
my $dt = DateTime->new(year => $year, month => 1, day => 1);
my $first_day = $dt->day_of_week;
my @workdaysinyear =
(
0, # dummy
261, # Monday - Monday
261, # Tuesday - Tuesday
261, # Wednesday - wednesday
261, # Thursday - Thursday
261, # Friday - Friday
260, # Saturday - Saturday
259, # Sunday - Sunday
);
my @add_leap =
(
0, # Dummy
1, # Mo -> Tu
1, # Tu -> We
1, # We -> Th
1, # Th -> Fr
0, # Fr -> Sa
0, # Sa -> Su
1, # Su -> Ma
);
my $count = $workdaysinyear[$first_day]
+ ($dt->is_leap_year ? $add_leap[$first_day] : 0);
say $count;

Running it gives the same result as the Raku version:

$ ./workdays-perl 2021
261
$ ./workdays-perl 2020
262

You are given a perfect square.

Write a script to figure out if the square root the given number is same as sum of 2 or more splits of the given number.

Example 1:

Write a script to figure out if the square root the given number is same as sum of 2 or more splits of the given number.

Example 1:

Input: $n = 81
Output: 1
Since, sqrt(81) = 8 + 1

Example 2:
Input: $n = 9801
Output: 1
Since, sqrt(9801) = 98 + 0 + 1

Example 3:
Input: $n = 36
Output: 0
Since, sqrt(36) != 3 + 6

A perfect square is the result of squaring another integer.

File: split-number#! /usr/bin/env raku
unit sub MAIN (Int $psq where $psq.sqrt.Int == $psq.sqrt && $psq > 9, # [1]
:v(:$verbose));
my $sqrt = $psq.sqrt; # [2]
my $size = $psq.chars; # [3]
say ": Square root of $psq = $sqrt" if $verbose;
my $found = False; # [4]
my $splits := gather # [5]
{
recurse( $psq, () ); # [5a]
}
for $splits -> $candidate # [6]
{
my @list = @$candidate; # [6a]
if @list.elems == 1 # [7]
{
say ": Candidate list: { @list.join(", ") } (ignored size)" if $verbose;
next;
}
my $sum = @list.sum; # [8]
if $sqrt == $sum # [8a]
{
say ": Candidate list: { @list.join(", ") } - with correct sum $sqrt"
if $verbose;
say 1 unless $found; # [8b]
$verbose
?? ( $found = True )
!! exit; # [8c]
}
else
{
say ": Candidate list: { @list.join(", ") } (wrong sum $sum vs \
expected $sqrt)" if $verbose;
}
}
say 0 unless $found; # [9]
sub recurse ($remainder, @done) # [10]
{
for 1 .. $remainder.chars -> $count # [11]
{
my $partial = $remainder.substr(0, $count); # [12]
my $new = $remainder.substr($count); # [13]
my @so-far = @done.clone; # [14]
@so-far.push: $partial; # [15]
$new eq "" # [16]
?? take @so-far # [16a]
!! recurse($new, @so-far); # [16b]
}
}

[1] Ensure that the given integer is a perfect square. Also, that it has at least to digits (because of «2 or more splits of the given number», which requires at least 2 digits).

[2] The quare root.

[3] The number of digits (length of the string).

[4] In case of verbose mode, where we go through all the candidates even after finding a match.

[5] Setting up the sequence with `gather`

/`take`

is ideal here, with a recurisve procedure. The actual `take`

is hidden away
in the procedure (see [16a]).

See my Raku Gather,
I Take article or
docs.raku.org/syntax/gather take for more information about
`gather`

/`take`

.

[6] For each candidate, which is a list of values (substrings).

[7] Skip lists with 1 element (i.e. no split at all).

[8] Get the sum. Do we have a match [8a]? If so, print «1» [8b] and exit [8c].

[9] No match? Then say so.

[10] The recursive procedure. The first argument is the remainder of the original number that has not been processes yet. The second is the list of splits so far in this iteration.

[11] We can have from 1 to the entire string characters in each split. (We do not need the whole string, but it felt right to make a general procedure).

[12] Get the new partial,

[13] and the new remainder.

[14] The list of splits, which we clone so that we do not clobber up the values for the other iterations.

See
docs.raku.org/routine/clone for
more information about the `clone`

method.

[15] Add the new partial to the list.

[16] No remainder? If so return the value (with `take`

).

Running it:

$ ./split-number 81
1
$ ./split-number 9801
1
$ ./split-number 36
0

Looking good.

With verbose mode:

$ ./split-number -v 81
: Square root of 81 = 9
: Candidate list: 8, 1 - with correct sum 9
1
: Candidate list: 81 (ignored size)
$ ./split-number -v 9801
: Square root of 9801 = 99
: Candidate list: 9, 8, 0, 1 (wrong sum 18 vs expected 99)
: Candidate list: 9, 8, 01 (wrong sum 18 vs expected 99)
: Candidate list: 9, 80, 1 (wrong sum 90 vs expected 99)
: Candidate list: 9, 801 (wrong sum 810 vs expected 99)
: Candidate list: 98, 0, 1 - with correct sum 99
1
: Candidate list: 98, 01 - with correct sum 99
: Candidate list: 980, 1 (wrong sum 981 vs expected 99)
: Candidate list: 9801 (ignored size)
$ ./split-number -v 36
: Square root of 36 = 6
: Candidate list: 3, 6 (wrong sum 9 vs expected 6)
: Candidate list: 36 (ignored size)
0

And that's it.