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.