aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--challenge-175/athanasius/perl/ch-1.pl148
-rw-r--r--challenge-175/athanasius/perl/ch-2.pl142
-rw-r--r--challenge-175/athanasius/raku/ch-1.raku111
-rw-r--r--challenge-175/athanasius/raku/ch-2.raku188
4 files changed, 589 insertions, 0 deletions
diff --git a/challenge-175/athanasius/perl/ch-1.pl b/challenge-175/athanasius/perl/ch-1.pl
new file mode 100644
index 0000000000..bf4458cf4f
--- /dev/null
+++ b/challenge-175/athanasius/perl/ch-1.pl
@@ -0,0 +1,148 @@
+#!perl
+
+###############################################################################
+=comment
+
+Perl Weekly Challenge 175
+=========================
+
+TASK #1
+-------
+*Last Sunday*
+
+Submitted by: Mohammad S Anwar
+
+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
+
+=cut
+###############################################################################
+
+#--------------------------------------#
+# Copyright © 2022 PerlMonk Athanasius #
+#--------------------------------------#
+
+#==============================================================================
+=comment
+
+Assumptions
+-----------
+All dates are interpreted according to the Gregorian calendar [1,3]. Dates
+prior to 15th October, 1582, when the Gregorian calendar was first introduced,
+are calculated according to the "proleptic Gregorian calendar" [3] which
+extends backwards to dates before the calendar was in use. However, dates prior
+to the year 1 AD are not supported.
+
+Solution
+--------
+The solution uses the CPAN module DateTime [2]. Its last_day_of_month()
+constructor automatically finds the last day of February as 28 or 29, according
+to whether or not the year is a leap year.
+
+Then, DateTime's day_of_the_week() method finds the weekday of the last day in
+the month: it "[r]eturns the day of the week as a number, from 1..7, with 1
+being Monday and 7 being Sunday." [2] If the last day is a Monday, then the
+last Sunday is one day prior; if a Tuesday, it is two days prior; and so on.
+Hence, the date of the last Sunday is calculated by subtracting from the date
+of the last day the weekday of the last day modulo 7.
+
+References
+----------
+[1] Claus Tøndering, "The Gregorian calendar", The Calendar FAQ,
+ https://www.tondering.dk/claus/cal/gregorian.php
+[2] Dave Rolsky, "DateTime", MetaCPAN, https://metacpan.org/pod/DateTime
+[3] "Gregorian calendar", Wikipedia,
+ https://en.wikipedia.org/wiki/Gregorian_calendar
+
+=cut
+#==============================================================================
+
+use strict;
+use warnings;
+use Const::Fast;
+use DateTime;
+use Regexp::Common qw( number );
+
+const my $USAGE =>
+"Usage:
+ perl $0 <year>
+
+ <year> A year in the Gregorian calendar (AD)\n";
+
+#------------------------------------------------------------------------------
+BEGIN
+#------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 175, Task #1: Last Sunday (Perl)\n\n";
+}
+
+#==============================================================================
+MAIN:
+#==============================================================================
+{
+ my $year = parse_command_line();
+
+ print "The last Sunday of each month in the year $year:\n\n";
+
+ for my $month (1 .. 12)
+ {
+ my $last_day_dt = DateTime->last_day_of_month
+ (
+ year => $year,
+ month => $month,
+ );
+ my $last_day = $last_day_dt->day;
+ my $day_of_week = $last_day_dt->day_of_week;
+ my $last_sun_dt = DateTime->new
+ (
+ year => $year,
+ month => $month,
+ day => $last_day - ($day_of_week % 7),
+ );
+
+ printf " %s\n", $last_sun_dt->ymd;
+ }
+}
+
+#------------------------------------------------------------------------------
+sub parse_command_line
+#------------------------------------------------------------------------------
+{
+ my $args = scalar @ARGV;
+ $args == 1 or error( "Expected 1 command line argument, found $args" );
+
+ my $year = $ARGV[ 0 ];
+
+ $year =~ / ^ $RE{num}{int} $ /x
+ or error( qq[Argument "$year" is not a valid integer] );
+
+ $year >= 1 or error( qq[Year "$year" is not AD] );
+
+ return $year;
+}
+
+#------------------------------------------------------------------------------
+sub error
+#------------------------------------------------------------------------------
+{
+ my ($message) = @_;
+
+ die "ERROR: $message\n$USAGE";
+}
+
+###############################################################################
diff --git a/challenge-175/athanasius/perl/ch-2.pl b/challenge-175/athanasius/perl/ch-2.pl
new file mode 100644
index 0000000000..d2f832d4aa
--- /dev/null
+++ b/challenge-175/athanasius/perl/ch-2.pl
@@ -0,0 +1,142 @@
+#!perl
+
+###############################################################################
+=comment
+
+Perl Weekly Challenge 175
+=========================
+
+TASK #2
+-------
+*Perfect Totient Numbers*
+
+Submitted by: Mohammad S Anwar
+
+Write a script to generate first 20 Perfect Totient Numbers. Please checkout
+[ https://en.wikipedia.org/wiki/Perfect_totient_number |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
+
+=cut
+###############################################################################
+
+#--------------------------------------#
+# Copyright © 2022 PerlMonk Athanasius #
+#--------------------------------------#
+
+#==============================================================================
+=comment
+
+Totients and Iterated Totients
+------------------------------
+"In number theory, Euler's totient function counts the positive integers up to
+a given integer n that are relatively prime to n. ... In other words, it is the
+number of integers k in the range 1 ≤ k ≤ n for which the greatest common
+divisor gcd(n, k) is equal to 1." [5] For all n > 2, φ(n) is even.
+
+Since gcd(n, n) = n, it follows that, for all n > 1, φ(n) is at most n - 1
+(when happens when n is prime), so φ(n) < n. So when totients are iterated, the
+iterations always eventually decrease to 2, and then to 1.
+
+Perfect Totient Numbers
+-----------------------
+AFAICT, the definitions of Perfect Totient Numbers (PTNs) given in [6] and [2]
+imply that 1 is a PTN, which it is not. A more rigorous definition is given in
+[4], which specifies that a PTN must be > 2.
+
+"It is trivial that perfect totient numbers must be odd. It is easy to show
+that powers of 3 are perfect totient numbers." [2]
+
+Solution
+--------
+The CPAN module Math::Prime::Util [3] provides function euler_phi(), which cal-
+culates the totient of a given integer very quickly. (For a home-grown imple-
+mentation of euler_phi(), see the Raku solution to Task 2.)
+
+Subroutine iterated_totient_sum() calls euler_phi() repeatedly until the itera-
+tions reduce to 2. The iterations are summed progressively, and finally 1 is
+added for φ(2).
+
+The main routine calls iterated_totient_sum() repeatedly on successive odd
+numbers: those for which the iterated totient sum equals the number itself are
+PTNs. The search continues until $TARGET PTNs have been found and displayed.
+
+References
+----------
+[1] "A000010 Euler totient function phi(n): count numbers <= n and prime to
+ n.", OEIS, https://oeis.org/A000010
+[2] "A082897 Perfect totient numbers.", OEIS, https://oeis.org/A082897
+[3] Dana Jacobsen, "Math::Prime::Util", MetaCPAN,
+ https://metacpan.org/pod/Math::Prime::Util
+[4] Douglas E. Iannucci, Deng Moujie, and Graeme L. Cohen, "On Perfect Totient
+ Numbers", Journal of Integer Sequences, Vol. 6 (2003),
+ http://www.emis.de/journals/JIS/VOL6/Cohen2/cohen50.pdf
+[5] "Euler's totient function", Wikipedia,
+ https://en.wikipedia.org/wiki/Euler%27s_totient_function
+[6] "Perfect totient number", Wikipedia,
+ https://en.wikipedia.org/wiki/Perfect_totient_number
+
+=cut
+#==============================================================================
+
+use strict;
+use warnings;
+use Const::Fast;
+use Math::Prime::Util qw( euler_phi );
+
+const my $TARGET => 20;
+const my $USAGE => "Usage:\n perl $0\n";
+
+#------------------------------------------------------------------------------
+BEGIN
+#------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 175, Task #2: Perfect Totient Numbers (Perl)\n\n";
+}
+
+#==============================================================================
+MAIN:
+#==============================================================================
+{
+ my $args = scalar @ARGV;
+ $args == 0 or die 'ERROR: Expected 0 command line arguments, found ' .
+ "$args\n$USAGE";
+
+ print "The first $TARGET Perfect Totient Numbers:\n\n3";
+
+ my $count = 1;
+
+ for (my $n = 5; $count < $TARGET; $n += 2)
+ {
+ if (iterated_totient_sum( $n ) == $n)
+ {
+ print ", $n";
+ ++$count;
+ }
+ }
+
+ print "\n";
+}
+
+#------------------------------------------------------------------------------
+sub iterated_totient_sum
+#------------------------------------------------------------------------------
+{
+ my ($n) = @_;
+ my $sum = 0;
+
+ while ($n > 2)
+ {
+ $n = euler_phi( $n );
+ $sum += $n;
+ }
+
+ return ++$sum; # euler_phi(2) = 1
+}
+
+###############################################################################
diff --git a/challenge-175/athanasius/raku/ch-1.raku b/challenge-175/athanasius/raku/ch-1.raku
new file mode 100644
index 0000000000..81586e5826
--- /dev/null
+++ b/challenge-175/athanasius/raku/ch-1.raku
@@ -0,0 +1,111 @@
+use v6d;
+
+###############################################################################
+=begin comment
+
+Perl Weekly Challenge 175
+=========================
+
+TASK #1
+-------
+*Last Sunday*
+
+Submitted by: Mohammad S Anwar
+
+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
+
+=end comment
+###############################################################################
+
+#--------------------------------------#
+# Copyright © 2022 PerlMonk Athanasius #
+#--------------------------------------#
+
+#==============================================================================
+=begin comment
+
+Assumptions
+-----------
+All dates are interpreted according to the Gregorian calendar [2,3]. Dates
+prior to 15th October, 1582, when the Gregorian calendar was first introduced,
+are calculated according to the "proleptic Gregorian calendar" [3] which
+extends backwards to dates before the calendar was in use. However, dates prior
+to the year 1 AD are not supported.
+
+Solution
+--------
+The solution uses Raku's native Date class [1]. Its constructor accepts a "*"
+as the argument to its "day" parameter: this gives the last day in the month,
+automatically finding the last day of February as 28 or 29, according to
+whether or not the year is a leap year.
+
+Then, Date's day-of-week() method finds the weekday of the last day in the
+month: it "[r]eturns the day of the week, where 1 is Monday, 2 is Tuesday and
+Sunday is 7." [1] If the last day is a Monday, then the last Sunday is one day
+prior; if a Tuesday, it is two days prior; and so on. Hence, the date of the
+last Sunday is calculated by subtracting from the date of the last day the
+weekday of the last day modulo 7.
+
+References
+----------
+[1] "class Date", Raku Documentation, https://docs.raku.org/type/Date
+[2] Claus Tøndering, "The Gregorian calendar", The Calendar FAQ,
+ https://www.tondering.dk/claus/cal/gregorian.php
+[3] "Gregorian calendar", Wikipedia,
+ https://en.wikipedia.org/wiki/Gregorian_calendar
+
+=end comment
+#==============================================================================
+
+#------------------------------------------------------------------------------
+BEGIN
+#------------------------------------------------------------------------------
+{
+ "\nChallenge 175, Task #1: Last Sunday (Raku)\n".put;
+}
+
+#==============================================================================
+sub MAIN
+(
+ UInt:D $year where * > 0 #= A year in the Gregorian calendar (AD)
+)
+#==============================================================================
+{
+ "The last Sunday of each month in the year $year:\n".put;
+
+ for 1 .. 12 -> UInt $month
+ {
+ my Date $last-day = Date.new: $year, $month, *;
+ my Date $last-sun = Date.new: $last-day - ($last-day.day-of-week % 7);
+
+ " { $last-sun.Str }".put;
+ }
+}
+
+#------------------------------------------------------------------------------
+sub USAGE()
+#------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s/ ($*PROGRAM-NAME) /raku $0/;
+
+ $usage.put;
+}
+
+###############################################################################
diff --git a/challenge-175/athanasius/raku/ch-2.raku b/challenge-175/athanasius/raku/ch-2.raku
new file mode 100644
index 0000000000..dea3154069
--- /dev/null
+++ b/challenge-175/athanasius/raku/ch-2.raku
@@ -0,0 +1,188 @@
+use v6d;
+
+###############################################################################
+=begin comment
+
+Perl Weekly Challenge 175
+=========================
+
+TASK #2
+-------
+*Perfect Totient Numbers*
+
+Submitted by: Mohammad S Anwar
+
+Write a script to generate first 20 Perfect Totient Numbers. Please checkout
+[ https://en.wikipedia.org/wiki/Perfect_totient_number |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
+
+=end comment
+###############################################################################
+
+#--------------------------------------#
+# Copyright © 2022 PerlMonk Athanasius #
+#--------------------------------------#
+
+#==============================================================================
+=begin comment
+
+Totients and Iterated Totients
+------------------------------
+"In number theory, Euler's totient function counts the positive integers up to
+a given integer n that are relatively prime to n. ... In other words, it is the
+number of integers k in the range 1 ≤ k ≤ n for which the greatest common
+divisor gcd(n, k) is equal to 1." [5] For all n > 2, φ(n) is even.
+
+Since gcd(n, n) = n, it follows that, for all n > 1, φ(n) is at most n - 1
+(when happens when n is prime), so φ(n) < n. So when totients are iterated, the
+iterations always eventually decrease to 2, and then to 1.
+
+Perfect Totient Numbers
+-----------------------
+AFAICT, the definitions of Perfect Totient Numbers (PTNs) given in [6] and [2]
+imply that 1 is a PTN, which it is not. A more rigorous definition is given in
+[4], which specifies that a PTN must be > 2.
+
+"It is trivial that perfect totient numbers must be odd. It is easy to show
+that powers of 3 are perfect totient numbers." [2]
+
+Solution
+--------
+Function euler-phi() calculates φ(n) using Euler's product formula [5]:
+
+ φ(n) = n ∏ (1 - 1/p) where p ranges over the distinct primes that divide n.
+
+The prime factors of n are calculated by the recursive function prime-factors()
+which in turn uses Raku's native is-prime() method.
+
+Subroutine iterated-totient-sum() calls euler-phi() repeatedly until the itera-
+tions reduce to 2. The iterations are summed progressively, and finally 1 is
+added for φ(2).
+
+The main routine calls iterated-totient-sum() repeatedly on successive odd
+numbers: those for which the iterated totient sum equals the number itself are
+PTNs. The search continues until $TARGET PTNs have been found and displayed.
+For $TARGET = 20, the complete search takes 14 seconds on my machine.
+
+References
+----------
+[1] "A000010 Euler totient function phi(n): count numbers <= n and prime to
+ n.", OEIS, https://oeis.org/A000010
+[2] "A082897 Perfect totient numbers.", OEIS, https://oeis.org/A082897
+[3] Dana Jacobsen, "Math::Prime::Util", MetaCPAN,
+ https://metacpan.org/pod/Math::Prime::Util
+[4] Douglas E. Iannucci, Deng Moujie, and Graeme L. Cohen, "On Perfect Totient
+ Numbers", Journal of Integer Sequences, Vol. 6 (2003),
+ http://www.emis.de/journals/JIS/VOL6/Cohen2/cohen50.pdf
+[5] "Euler's totient function", Wikipedia,
+ https://en.wikipedia.org/wiki/Euler%27s_totient_function
+[6] "Perfect totient number", Wikipedia,
+ https://en.wikipedia.org/wiki/Perfect_totient_number
+
+=end comment
+#==============================================================================
+
+my UInt constant $TARGET = 20;
+my Bool constant $TIME = False;
+
+#------------------------------------------------------------------------------
+BEGIN
+#------------------------------------------------------------------------------
+{
+ "\nChallenge 175, Task #2: Perfect Totient Numbers (Raku)\n".put;
+}
+
+#==============================================================================
+sub MAIN()
+#==============================================================================
+{
+ my Int $t0 = time if $TIME;
+
+ "The first $TARGET Perfect Totient Numbers:\n\n3".print;
+
+ my UInt $count = 1;
+
+ loop (my UInt $n = 5; $count < $TARGET; $n += 2)
+ {
+ if (iterated-totient-sum( $n ) == $n)
+ {
+ ", $n".print;
+ ++$count;
+ }
+ }
+
+ put();
+
+ "\n%d seconds\n".printf: time - $t0 if $TIME;
+}
+
+#------------------------------------------------------------------------------
+sub iterated-totient-sum( UInt:D $n where { $n > 1 } --> UInt:D )
+#------------------------------------------------------------------------------
+{
+ my UInt $i = $n;
+ my UInt $sum = 0;
+
+ while $i > 2
+ {
+ $i = euler-phi( $i );
+ $sum += $i;
+ }
+
+ return ++$sum; # euler-phi(2) = 1
+}
+
+#------------------------------------------------------------------------------
+sub euler-phi( UInt:D $n where { $n > 1 } --> UInt:D )
+#------------------------------------------------------------------------------
+{
+ # phi(n) = n * Product_{distinct primes p dividing n} (1 - 1/p)
+
+ my UInt @prime-factors = prime-factors( $n ).unique;
+ my Rat $phi = $n * [*] @prime-factors.map: { 1 - (1 / $_) };
+
+ return $phi.Int;
+}
+
+#------------------------------------------------------------------------------
+sub prime-factors( UInt:D $n where { $n > 1 } --> Array:D[UInt:D] )
+#------------------------------------------------------------------------------
+{
+ my UInt @prime-factors;
+
+ if $n.is-prime
+ {
+ @prime-factors.push: $n;
+ }
+ else
+ {
+ for 2, 3, 5, 7 ... * -> UInt $p
+ {
+ if $p.is-prime && $n %% $p
+ {
+ @prime-factors.push: $p, |prime-factors( ($n / $p).Int );
+ last;
+ }
+ }
+ }
+
+ return @prime-factors;
+}
+
+#------------------------------------------------------------------------------
+sub USAGE()
+#------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s:g/ ($*PROGRAM-NAME) /raku $0/;
+
+ $usage.put;
+}
+
+###############################################################################