with Raku

This is my response to The Weekly Challenge #175.

Write a script to list

For example, for year 2022, we should get the following:

`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

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.

#! /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.