diff options
| -rw-r--r-- | challenge-175/athanasius/perl/ch-1.pl | 148 | ||||
| -rw-r--r-- | challenge-175/athanasius/perl/ch-2.pl | 142 | ||||
| -rw-r--r-- | challenge-175/athanasius/raku/ch-1.raku | 111 | ||||
| -rw-r--r-- | challenge-175/athanasius/raku/ch-2.raku | 188 |
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; +} + +############################################################################### |
