This is my response to The Weekly Challenge #175.
Last Sunday of every month in the given year.
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
This is almost the same as challenge #13.1 from June 2019 (Sunday instead of Friday this time). See Hofstadter, Friday and Raku for my take on that one.
File: last-sunday
#! /usr/bin/env raku
unit sub MAIN (Int $year = Date.today.year); # [1]
for 1 .. 12 -> $month # [2]
{
my $date = Date.new($year, # [3]
$month,
Date.new($year, $month, 1).days-in-month);
$date.=pred while $date.day-of-week != 7; # [4]
say $date; # [5]
}
[1] Default to the current year, which we get by getting the year from
the current date (set up with Date.today).
See
docs.raku.org/type/Date for more information about the Date class.
[2] For each month in the specified year,
[3] Get a Date object for the last day in the month, with another Date call
and the days-in-month method to get there.
[4] Subtract one day at a time (from the object), as long as the day
(day-of-week) is anything other than 7 (i.e. Sunday).
[5] Print the date. This stringifies to the required format.
Running it:
$ ./last-sunday
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
Looking good.
We can try another year:
$ ./last-sunday 2023
2023-01-29
2023-02-26
2023-03-26
2023-04-30
2023-05-28
2023-06-25
2023-07-30
2023-08-27
2023-09-24
2023-10-29
2023-11-26
2023-12-31
As a one-liner, shown here on multiple lines to make sort of it readable;
File: last-sunday-oneliner
#! /usr/bin/env raku
-> $year
{
-> $month
{
-> $date
{
say $date.earlier(days => $date.day-of-week == 7 ?? ( 0 ) !! ( $date.day-of-week ) )
}(Date.new($year, $month, Date.new($year, $month, 1).days-in-month))
}($_) for 1 .. 12
}(@*ARGS[0] // Date.today.year);
It gives the expected output:
$ ./last-sunday-oneliner
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
Write a script to generate first 20 Perfect Totient Numbers. Please checkout
wikipedia page for
more informations.
3, 9, 15, 27, 39, 81, 111, 183, 243, 255, 327, 363, 471, 729,
2187, 2199, 3063, 4359, 4375, 5571
#! /usr/bin/env raku
unit sub MAIN (Int $count where $count > 0 = 20, :v(:$verbose)); # [1]
my $ptm := (1 .. Inf).grep( *.&is-ptm ); # [1]
say $ptm[^$count].join(", "); # [1]
sub is-ptm ($number) # [2]
{
my @totients; # [3]
my $c = $number; # [4]
while $c > 1 # [5]
{
$c = totient($c); # [6]
@totients.push: $c; # [7]
}
say ":: $number [@totients[]] sum:{ @totients.sum } \
{ $number == @totients.sum ?? "match" !! "" }" if $verbose;
return @totients.sum == $number; # [8]
}
sub totient ($number) # [9]
{
my $count = 0; # [10]
for 1 .. $number -1 -> $candidate # [11]
{
$count++ if $number gcd $candidate == 1; # [12]
}
return $count; # [13]
}
[1] These lines of code should be familiar by now. If not, see e.g. Disarmed Ranking with Raku for a detailed description.
[2] Is the number in question a Perfect Totient Number?
[3] We do this by collecting the totients (in this list).
[4] We need a writeable copy of the initial number, as we change it iteratively (see [5]) - and we need the initial value (see [8]).
[5] Stop when we reach 1.
[6] Get the Totient number of the current value.
[7] Add the current Totient value to the list.
[8] We have a Perfect Totient Number if the sum of the totients is equal to the number itself.
[9] Totient transformation, as described by Wikipedia.
[10] The count (initially none).
[11] For each value (integer) from 1 to one less than the number itself.
[12] Add one to the count if the greatest common denominator of the number and the loop value is 1.
See
docs.raku.org/routine/gcd for more information about the Greatest Common Divisor operator
gcd.
[13] Return the count.
Running it:
$ ./ptm
3, 9, 15, 27, 39, 81, 111, 183, 243, 255, 327, 363, 471, 729, 2187, 2199, \
3063, 4359, 4375, 5571
Looking good.
Let us have a go at verbose mode, combined with a low number of values (to avoid information overload).
$ ./ptm -v 4
:: 1 [] sum:0
:: 2 [1] sum:1
:: 3 [2 1] sum:3 match
:: 4 [2 1] sum:3
:: 5 [4 2 1] sum:7
:: 6 [2 1] sum:3
:: 7 [6 2 1] sum:9
:: 8 [4 2 1] sum:7
:: 9 [6 2 1] sum:9 match
:: 10 [4 2 1] sum:7
:: 11 [10 4 2 1] sum:17
:: 12 [4 2 1] sum:7
:: 13 [12 4 2 1] sum:19
:: 14 [6 2 1] sum:9
:: 15 [8 4 2 1] sum:15 match
:: 16 [8 4 2 1] sum:15
:: 17 [16 8 4 2 1] sum:31
:: 18 [6 2 1] sum:9
:: 19 [18 6 2 1] sum:27
:: 20 [8 4 2 1] sum:15
:: 21 [12 4 2 1] sum:19
:: 22 [10 4 2 1] sum:17
:: 23 [22 10 4 2 1] sum:39
:: 24 [8 4 2 1] sum:15
:: 25 [20 8 4 2 1] sum:35
:: 26 [12 4 2 1] sum:19
:: 27 [18 6 2 1] sum:27 match
3, 9, 15, 27
I have highlighted the Perfect Totient Numbers in green.
And that's it.