Perl Weekly Challenge: Week 175
Challenge 1:
Last Sunday
Write a script to list
Last Sunday
of every month in the given year.For example, for year 2022, we should get the following:
2022-01-30
2022-02-27
2022-03-27
2022-04-24
2022-05-29
2022-06-26
2022-07-31
2022-08-28
2022-09-25
2022-10-30
2022-11-27
2022-12-25
It's been some time since we've had my favorite; a date-related challenge. Raku has all we need built in.
for 1..12 -> $month {
For each month in the designated year...
my $lastDate = Date.new($year, $month, 1).last-date-in-month;
...we create an object of class Date
and initialize it with the year and month. As we also have to provide a day to the constructor, we set that to 1. This object has a method which returns another Date
representing the last date in the month. We capture this in the $lastDate
variable.
say $lastDate - $lastDate.day-of-week % 7;
Date
also has a method day-of-week()
which returns an integer from 1 to 7 where 1 equals Monday and 7 is Sunday. If we take that value modulo 7, we will know how many days to subtract from $lastDate
in order to get the last Sunday. We don't need to do anything
special to print this out as the default representation for Date
is yyyy-mm-dd which is what the spec asks for,
}
For Perl I used the DateTime
CPAN module for the Date arithmetic.
for my $month (1..12) {
Once again I applied the calculation to each month.
my $lastDate = DateTime->last_day_of_month(year => $year, month => $month);
The DateTime
class has a constructor last_day_of_month()
which given a year and month (day is not required unlike Raku.) returns
a new DateTime
object representing the last day of that month.
$lastDate ->subtract(days => $lastDate->dow % 7);
The dow()
method works the same as Date.day-of-week()
in Raku. The result modulo 7 is the number of days to substract from $lastDate
(using the subtreact()
method) to get the last Sunday.
say $lastDate->ymd;
In order to get the yyyy-mm-dd format, we need the ymd()
method.
}
Challenge 2:
Perfect Totient Numbers
Write a script to generate first
20 Perfect Totient Numbers
. Please checkout wikipedia page for more informations.
Output
3, 9, 15, 27, 39, 81, 111, 183, 243, 255, 327, 363, 471, 729,
2187, 2199, 3063, 4359, 4375, 5571
Yet another number series. So my MAIN()
function follows the same structure I've used for similar challenges.
my @perfectTotients;
my $n = 1;
while @perfectTotients.elems < 20 {
if isPerfectTotient($n) {
@perfectTotients.push($n);
}
$n++;
}
@perfectTotients.join(q{, }).say;
Starting from 1, we go through consecutive integers collected perfect totient numbers until we have 20 and then printing them
out. The isPerfectTotient()
function does all the heavy lifting.
Originally the function used recursion but I changed it to this way to make it a little faster and give my laptops fan a break especially on the larger numbers.
sub isPerfectTotient(Int $n) {
my $total = 0;
my $current = $n;
while $current != 0 {
my $totients = totients($current);
$total += $totients;
$current = $totients;
}
Starting with $n
(which is copied to $current
because function parameters are immutable by default in Raku) a while loop is run as long as $current
is not zero. In it, $current
is passed to another function called totients()
which returns the number of totients (Am I using this word correctly? They are also known as relative primes or coprimes) it has. This result is stored in a variable called $totients
which is not very good software engineering practice though Raku has no issues with a variable and a function having the same name. $totients
is added to $total
and becomes the next value of $current
.
return $total == $n;
Once the loop is completed, the $total
number of totients is compared to $n
. If the two are equal, $n
is a perfect totient and
True
is returned otherwise False
.
}
As mentioned previously, a function called totients()
actually counts the totients.
sub totients(Int $n) {
my $tots = 0;
for 1 ..^ $n -> $i {
if $i gcd $n == 1 {
$tots++;
}
}
It works by going through all positive integers before $n
. If such a number and $n
have a greatest common divisor of 1, it means
it is a totient. Raku has a handy gcd
operator we can use.
I notice that atleast here I had the presence of mind to call the result variable something other than $totients
.
return $tots;
}
Perl doesn't have a gcd
operator like Raku so the first order of business was to write a substitute. Or more accurately, to scour through previous challenges to see if I had written one. Which, in fact, I did but this function is recursive which probably helps explain why the Perl version was uncharacteristically slower than the Raku version.
sub gcd {
my ($a, $b) = @_;
return 0 == $b ? $a : gcd($b, $a % $b);
}
The rest of the script pretty much looks the same as in Raku.
my @perfectTotients;
my $n = 1;
while (scalar @perfectTotients < 20) {
if (isPerfectTotient($n)) {
push @perfectTotients, $n;
}
$n++;
}
say join q{, }, @perfectTotients;
sub isPerfectTotient {
my ($n) = @_;
my $total = 0;
my $current = $n;
while ($current != 0) {
my $totients = totients($current);
$total += $totients;
$current = $totients;
}
return $total == $n;
}
sub totients {
my ($n) = @_;
my $tots = 0;
for my $i (1 .. $n - 1) {
if (gcd($i, $n) == 1) {
$tots++;
}
}
return $tots;
}