diff options
| author | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2022-06-05 19:48:01 +1000 |
|---|---|---|
| committer | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2022-06-05 19:48:01 +1000 |
| commit | d8391aa1e90bd8db6046b3d25421ab75a840dbeb (patch) | |
| tree | 67d274c44d0d07b5404266c973ec78fd55674c44 /challenge-167 | |
| parent | 18a163895ca6d1cc72f209a9a9ba6a4713ab8ebc (diff) | |
| download | perlweeklychallenge-club-d8391aa1e90bd8db6046b3d25421ab75a840dbeb.tar.gz perlweeklychallenge-club-d8391aa1e90bd8db6046b3d25421ab75a840dbeb.tar.bz2 perlweeklychallenge-club-d8391aa1e90bd8db6046b3d25421ab75a840dbeb.zip | |
Perl & Raku solutions to Tasks 1 & 2 for Week 167
Diffstat (limited to 'challenge-167')
| -rw-r--r-- | challenge-167/athanasius/perl/ch-1.pl | 285 | ||||
| -rw-r--r-- | challenge-167/athanasius/perl/ch-2.pl | 326 | ||||
| -rw-r--r-- | challenge-167/athanasius/raku/ch-1.raku | 293 | ||||
| -rw-r--r-- | challenge-167/athanasius/raku/ch-2.raku | 296 |
4 files changed, 1200 insertions, 0 deletions
diff --git a/challenge-167/athanasius/perl/ch-1.pl b/challenge-167/athanasius/perl/ch-1.pl new file mode 100644 index 0000000000..f15ab60584 --- /dev/null +++ b/challenge-167/athanasius/perl/ch-1.pl @@ -0,0 +1,285 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 167 +========================= + +TASK #1 +------- +*Circular Prime* + +Submitted by: Mohammad S Anwar + +Write a script to find out first 10 circular primes having at least 3 digits +(base 10). + +Please checkout [ https://en.wikipedia.org/wiki/Circular_prime |wikipedia] for +more information. + + + A circular prime is a prime number with the property that the number + generated at each intermediate step when cyclically permuting its (base + 10) digits will also be prime. + + +Output + + 113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933 + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +Note +---- +There are 2 definitions of "circular primes": + + 1. "Numbers such that every cyclic permutation is a prime" [2]: + 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, 97, 113, 131, 197, 199, ... + 2. As in 1, except that "Only the smallest member of the cyclic shift is + listed." [1]: 2, 3, 5, 7, 11, 13, 17, 37, 79, 113, 197, 199, 337, 1193, ... + +From the output shown in the Task description, it is evident that the circular +primes required here are those which satisfy definition 2. + +Algorithm +--------- +In base 10, all circular primes of 2 or more digits must be composed of the +digits 1, 3, 7, or 9 only. + Rationale: + any 2+ -digit integer ending in 0 is divisible by 2 and 5; + any 2+ -digit integer ending in 2, 4, 6, or 8 is divisible by 2; + any 2+ -digit integer ending in 5 is divisible by 5; + and in any cycle, each digit must appear at least once in final position. + +Accordingly, the algorithm begins by *constructing* possible solutions (i.e., +integers containing just those digits): + +1. Let circ_primes = () +2. FOR d = 3, 4, 5, ... UNTIL circ_primes contains 10 elements + 3. Find all combinations of d digits drawn from the set {1, 3, 7, 9}. + As these are *combinations*, order is unimportant. For convenience, + the digits in each combination are sorted in ascending numerical + order. + 4. Filter out combinations containing all 3s, all 7s, and all 9s. + Rationale: Any integer of 2 or more digits in which the digits + are all 3, 7, or 9 is divisible by the repunit of the same + length. For example, 7777 = 1111 * 7. + 5. FOR each combination c + 6. Generate all the permutations of the digits in c + 7. WHILE there are permutations remaining + 8. Remove the first permutation and store as p + 9. Generate the cycle of integers beginning with p + 10. If every member of the cycle is prime, store p in + circ_primes + 11. Remove all members of the cycle from the remaining + permutations + ENDWHILE + ENDFOR + ENDFOR +12. Display the contents of circ_primes + +Table of Circular Primes +------------------------ +From [1]: 2, 3, 5, 7, 11, 13, 17, 37, 79, + 113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933, + 1111111111111111111, 11111111111111111111111 + +References +---------- +[1] "A016114 Circular primes (numbers that remain prime under cyclic shifts of + digits).", OEIS, https://oeis.org/A016114 +[2] "A068652 Numbers such that every cyclic permutation is a prime.", OEIS, + https://oeis.org/A068652 +[3] "Circular prime", Wikipedia, https://en.wikipedia.org/wiki/Circular_prime + +=cut +#============================================================================== + +use strict; +use warnings; +use Algorithm::Loops qw( NestedLoops NextPermute ); +use Const::Fast; +use List::MoreUtils qw( bremove ); +use Math::Prime::Util qw( is_prime ); + +const my $MIN_DIGITS => 3; +const my $TARGET => 10; +const my $USAGE => "Usage:\n perl $0\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 167, Task #1: Circular Prime (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my $args = scalar @ARGV; + $args == 0 or die 'ERROR: Expected 0 command line arguments, found ' . + "$args\n$USAGE"; + + my @circ_primes; + + L_OUTER: for (my $digits = $MIN_DIGITS; ; ++$digits) + { + my $combs = get_combinations( $digits ); + $combs = filter_reps( $combs ); + + for my $comb (@$combs) + { + my $new_circ_primes = find_circular_primes( $comb ); + + push @circ_primes, @$new_circ_primes; + + last L_OUTER if scalar @circ_primes >= $TARGET; + } + } + + printf "Output: %s\n", join ', ', @circ_primes[ 0 .. $TARGET - 1 ]; +} + +#------------------------------------------------------------------------------ +sub find_circular_primes +#------------------------------------------------------------------------------ +{ + my ($num) = @_; + my $perms = get_permutations( $num ); + my @circ_primes; + + while (scalar @$perms) + { + my $perm = shift @$perms; + my $cycle = get_cycle( $perm ); + my $all_prime = 1; + + for my $n (@$cycle) + { + unless (is_prime( $n )) + { + $all_prime = 0; + last; + } + } + + push @circ_primes, $perm if $all_prime; + + for my $n (@$cycle) + { + bremove { $_ <=> $n } @$perms; + } + } + + return \@circ_primes; +} + +#------------------------------------------------------------------------------ +sub get_permutations +#------------------------------------------------------------------------------ +{ + my ($n) = @_; + my @digits = split '', $n; # Note: the digits are already sorted (asc.) + my @perms; + + do + { + my $perm = join '', @digits; + + push @perms, $perm; + + } while (NextPermute( @digits )); + + return \@perms; +} + +#------------------------------------------------------------------------------ +sub get_cycle # E.g., 137 -> 713 -> 371 [-> 137] +#------------------------------------------------------------------------------ +{ + my ($first) = @_; + my @cycle = $first; + my @digits = split '', $first; + my $next; + + do + { + my $last = pop @digits; + @digits = ($last, @digits); + $next = join '', @digits; + + push @cycle, $next; + + } until ($next eq $first); + + pop @cycle; + + return \@cycle; +} + +#------------------------------------------------------------------------------ +sub get_combinations +#------------------------------------------------------------------------------ +{ + my ($depth) = @_; + my @combs; + + NestedLoops + ( + [ + [ 1, 3, 7, 9 ], + ( + sub + { + $_ == 1 ? [ 1, 3, 7, 9 ] : + $_ == 3 ? [ 3, 7, 9 ] : + $_ == 7 ? [ 7, 9 ] : + [ 9 ]; + } + + ) x ($depth - 1), + ], + sub + { + push @combs, join '', @_; + }, + ); + + return \@combs; +} + +#------------------------------------------------------------------------------ +sub filter_reps # Remove 333, 777, 999, 3333, ..., but NOT 111, 1111, ... +#------------------------------------------------------------------------------ +{ + my ($combs) = @_; + my @filtered; + + for my $comb (@$combs) + { + my @digits = split '', $comb; + my %count; + + ++$count{ $_ } for @digits; + + if (scalar( keys %count ) > 1 || exists $count{ '1' }) + { + push @filtered, $comb; + } + } + + return \@filtered; +} + +############################################################################### diff --git a/challenge-167/athanasius/perl/ch-2.pl b/challenge-167/athanasius/perl/ch-2.pl new file mode 100644 index 0000000000..d56f2156f1 --- /dev/null +++ b/challenge-167/athanasius/perl/ch-2.pl @@ -0,0 +1,326 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 167 +========================= + +TASK #2 +------- +*Gamma Function* + +Submitted by: Mohammad S Anwar + +Implement subroutine gamma() using the [ https://en.wikipedia.org/wiki/Lanczos_ +approximation |Lanczos approximation] method. + +[2022-05-31] + Ryan Thompson wrote an interesting blog explaining the subject in details. + Highly recommended if you are looking for more information. [ https://ry.ca/ + 2022/ 05/lanczos-approximation |BLOG]. + +Example + + print gamma(3); # 1.99 + print gamma(5); # 24 + print gamma(7); # 719.99 + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +Command-Line Interface +---------------------- +2 arguments: Real and imaginary parts of the complex argument to the gamma + function +1 argument: Real part as above; the imaginary part defaults to zero +0 arguments: Run the test suite (expected values obtained from [4] and [2]). + +Algorithm +--------- +A port from Python to Perl of the "Simple implementation" in [3]. Note: I have +departed from the Task specification by implementing a separate factorial +calculation for positive integers. This gives more accurate results: e.g., +gamma(3) = 2 (which is exactly correct) rather than 1.99 as in the Example. + +References +---------- +[1] "Gamma function", Wikipedia, https://en.wikipedia.org/wiki/Gamma_function +[2] "Gamma function Calculator", ke!san Online Calculator, + https://keisan.casio.com/exec/system/1180573444 +[3] "Lanczos approximation", Wikipedia, https://en.wikipedia.org/wiki/Lanczos_ + approximation +[4] "Particular values of the gamma function", Wikipedia, + https://en.wikipedia.org/wiki/Particular_values_of_the_gamma_function + +=cut +#============================================================================== + +use strict; +use warnings; +use Const::Fast; +use Devel::Assert qw( on ); +use Math::Complex qw( cplx Inf pi ); +use Regexp::Common; + +const my $ACCURACY => 13; # Significant digits +const my $EPSILON => 1e-12; +const my $INIT_X => 0.99999999999980993; +const my @P_COEFS => + ( + 676.5203681218851, + -1259.1392167224028, + 771.32342877765313, + -176.61502916214059, + 12.507343278686905, + -0.13857109526572012, + 9.9843695780195716e-6, + 1.5056327351493116e-7, + ); +const my $COMPLEX_RE => qr/ ($RE{num}{real}) ([+-]) ($RE{num}{real}) i /x; +const my $USAGE => +"Usage: + perl $0 [<re>] [<im>] + + [<re>] Real part + [<im>] Imaginary part [default: 0]\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 167, Task #2: Gamma Function (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my $z = parse_command_line(); + + if (defined $z) + { + assert( ref $z eq 'Math::Complex' ); + + printf "gamma(%s) = %s\n", $z, gamma( $z ); + } + else + { + test(); + } +} + +#------------------------------------------------------------------------------ +sub gamma +#------------------------------------------------------------------------------ +{ + my ($z) = @_; + + assert( ref $z eq 'Math::Complex' ); + + my $y = cplx( Inf, 0 ); # Assume failure (use Inf to represent NaN) + + if (is_int( $z )) + { + # Calculate the factorial directly, for better accuracy + + $y = cplx( factorial( $z->Re - 1 ), 0 ) if $z->Re > 0; + } + elsif ($z->Re < 0.5) # Reflexion: recursive call + { + $y = pi / (sin( pi * $z ) * gamma( 1 - $z )); + } + else # Apply the Lanczos approximation + { + $z -= 1; + my $x = $INIT_X; + my $i = 0; + + $x += $_ / ($z + $i++ + 1) for @P_COEFS; + + my $t = $z + scalar( @P_COEFS ) - 0.5; + $y = sqrt( 2 * pi ) * $t ** ($z + 0.5) * exp( -$t ) * $x; + } + + $y = cplx( $y->Re, 0 ) if abs( $y->Im ) <= $EPSILON; # Drop imag. part + + return $y; +} + +#------------------------------------------------------------------------------ +sub is_int +#------------------------------------------------------------------------------ +{ + my ($z) = @_; + + assert( ref $z eq 'Math::Complex' ); + + my $real = $z->Re; + + return abs( $z->Im ) < $EPSILON && + abs( $real - int $real ) < $EPSILON; +} + +#------------------------------------------------------------------------------ +sub factorial +#------------------------------------------------------------------------------ +{ + my ($n) = @_; + + assert( $n >= 0 ); + + my $fact = 1; + + for my $i (2 .. $n) + { + $fact *= $i; + } + + return $fact; +} + +#------------------------------------------------------------------------------ +sub test +#------------------------------------------------------------------------------ +{ + require Test::More; + Test::More->import; + + while (my $line = <DATA>) + { + chomp $line; + $line =~ s/ ^ \s+ //x; + $line =~ s/ \s+ $ //x; + + my ($inp, $exp) = split / \s+ /x, $line, 2; + my $z_inp = extract_cplx( $inp ); + my $z_exp = ($exp eq 'NaN') ? cplx( Inf ) : extract_cplx( $exp ); + my $z_got = round( gamma( $z_inp ) ); + $z_exp = round( $z_exp ); + + ok( cplx_eq( $z_got, $z_exp ), "gamma($z_inp)" ) + or printf "Expected %s,\n Got %s\n", $z_exp, $z_got; + } + + done_testing(); +} + +#------------------------------------------------------------------------------ +sub extract_cplx +#------------------------------------------------------------------------------ +{ + my ($str) = @_; + + my ($re, $sign, $im) = $str =~ $COMPLEX_RE; + + $im = -$im if $sign eq '-'; + + return cplx( $re, $im ); +} + +#------------------------------------------------------------------------------ +sub round +#------------------------------------------------------------------------------ +{ + my ($z) = @_; + + assert( ref $z eq 'Math::Complex' ); + + my $real = sprintf '%.*f', $ACCURACY - length int( $z->Re ), $z->Re; + my $imag = sprintf '%.*f', $ACCURACY - length int( $z->Im ), $z->Im; + + $real =~ s/ \. .* (0+) $ //x; # Trim any trailing "0" decimals + $imag =~ s/ \. .* (0+) $ //x; + $real =~ s/ \. $ //x; # Trim final decimal point (if any) + $imag =~ s/ \. $ //x; + + return cplx( $real, $imag ); +} + +#------------------------------------------------------------------------------ +sub cplx_eq +#------------------------------------------------------------------------------ +{ + my ($za, $zb) = @_; + + assert( ref $za eq 'Math::Complex' && + ref $zb eq 'Math::Complex' ); + + return ( $za->Re == Inf && $zb->Re == Inf ) || + (abs( $za->Re - $zb->Re ) < $EPSILON && + abs( $za->Im - $zb->Im ) < $EPSILON); +} + +#------------------------------------------------------------------------------ +sub parse_command_line +#------------------------------------------------------------------------------ +{ + my $args = scalar @ARGV; + 0 <= $args <= 2 + or error( "Expected 0, 1, or 2 command line arguments, found $args" ); + + return if $args == 0; + + my $re = $ARGV[ 0 ]; + + $re =~ / ^ $RE{num}{real} $ /x + or error( qq[Real part "$re" is not a valid real number] ); + + my $im = ($args == 2) ? $ARGV[ 1 ] : 0; + + $im =~ / ^ $RE{num}{real} $ /x + or error( qq[Imaginary part "$im" is not a valid real number] ); + + return cplx( $re, $im ); +} + +#------------------------------------------------------------------------------ +sub error +#------------------------------------------------------------------------------ +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +############################################################################### + +__DATA__ + 3+0i 2+0i + 5+0i 24+0i + 7+0i 720+0i + 1+0i 1+0i + 2+0i 1+0i + 4+0i 6+0i + 0+0i NaN +-1+0i NaN + 0.5+0i 1.77245385090551+0i + 1.5+0i 0.88622692545275+0i + 2.5+0i 1.32934038817913+0i + 3.5+0i 3.32335097044784+0i +-0.5+0i -3.54490770181103+0i +-1.5+0i 2.36327180120735+0i +-2.5+0i -0.94530872048294+0i + 0+1i -0.15494982830181-0.49801566811835i + 1+1i 0.49801566811835-0.15494982830181i + 1-1i 0.49801566811835+0.15494982830181i + 0.5+0.5i 0.81816399954174-0.76331382871398i + 0.5-0.5i 0.81816399954174+0.76331382871398i + 5+3i 0.01604188274165-9.43329328975598i + 5-3i 0.01604188274165+9.43329328975598i + 1.46163214496836+0i 0.88560319441088+0i + 0.75+0.75i 0.59665430268224-0.37676090720240i + 0.75-2.75i 0.03902232213674-0.01782079536849i + 3.14159265358979+3.14159265358979i -0.42793915904009-0.21380749181397i +10+0i 362880+0i +12-11.5i -60802.08246207147008+249916.64966162368028i + 0.33333333333333+0i 2.67893853470774+0i + 0.14285714285714+0i 6.54806294024782+0i diff --git a/challenge-167/athanasius/raku/ch-1.raku b/challenge-167/athanasius/raku/ch-1.raku new file mode 100644 index 0000000000..23d73befdd --- /dev/null +++ b/challenge-167/athanasius/raku/ch-1.raku @@ -0,0 +1,293 @@ +use v6d; + +############################################################################### +=begin comment + +Perl Weekly Challenge 167 +========================= + +TASK #1 +------- +*Circular Prime* + +Submitted by: Mohammad S Anwar + +Write a script to find out first 10 circular primes having at least 3 digits +(base 10). + +Please checkout [ https://en.wikipedia.org/wiki/Circular_prime |wikipedia] for +more information. + + + A circular prime is a prime number with the property that the number + generated at each intermediate step when cyclically permuting its (base + 10) digits will also be prime. + + +Output + + 113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933 + +=end comment +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=begin comment + +Note +---- +There are 2 definitions of "circular primes": + + 1. "Numbers such that every cyclic permutation is a prime" [2]: + 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, 97, 113, 131, 197, 199, ... + 2. As in 1, except that "Only the smallest member of the cyclic shift is + listed." [1]: 2, 3, 5, 7, 11, 13, 17, 37, 79, 113, 197, 199, 337, 1193, ... + +From the output shown in the Task description, it is evident that the circular +primes required here are those which satisfy definition 2. + +Algorithm +--------- +In base 10, all circular primes of 2 or more digits must be composed of the +digits 1, 3, 7, or 9 only. + Rationale: + any 2+ -digit integer ending in 0 is divisible by 2 and 5; + any 2+ -digit integer ending in 2, 4, 6, or 8 is divisible by 2; + any 2+ -digit integer ending in 5 is divisible by 5; + and in any cycle, each digit must appear at least once in final position. + +Accordingly, the algorithm begins by *constructing* possible solutions (i.e., +integers containing just those digits): + +1. Let circ_primes = () +2. Find all combinations of 3-, 4-, 5-, and 6-digits drawn from the set + {1, 3, 7, 9}. Note: as these are *combinations*, order is unimportant. For + convenience, the digits in each combination are sorted in ascending numeri- + cal order. +3. Filter out combinations containing all 3s, all 7s, and all 9s. + Rationale: Any integer of 2 or more digits in which the digits are all + 3, 7, or 9 is divisible by the repunit of the same length. + For example, 7777 = 1111 * 7. +4. FOR each combination c + 5. Generate all the permutations of the digits in c + 6. WHILE there are permutations remaining + 7. Remove the first permutation and store as p + 8. Generate the cycle of integers beginning with p + 9. If every member of the cycle is prime, store p in + circ_primes + 10. Remove all members of the cycle from the remaining permuta- + tions + ENDWHILE + ENDFOR +11. Display the contents of circ_primes + +Table of Circular Primes +------------------------ +From [1]: 2, 3, 5, 7, 11, 13, 17, 37, 79, + 113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933, + 1111111111111111111, 11111111111111111111111 + +References +---------- +[1] "A016114 Circular primes (numbers that remain prime under cyclic shifts of + digits).", OEIS, https://oeis.org/A016114 +[2] "A068652 Numbers such that every cyclic permutation is a prime.", OEIS, + https://oeis.org/A068652 +[3] "Circular prime", Wikipedia, https://en.wikipedia.org/wiki/Circular_prime + +=end comment +#============================================================================== + +my constant @DIGITS = 1, 3, 7, 9; +my UInt constant $TARGET = 10; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + "\nChallenge 167, Task #1: Circular Prime (Raku)\n".put; +} + +#============================================================================== +sub MAIN() +#============================================================================== +{ + my UInt @combs = get-combinations(); + @combs = filter-reps( @combs ); + my UInt @circ-primes; + + for @combs -> UInt $comb + { + @circ-primes.push: |find-circular-primes( $comb ); + + last if +@circ-primes >= $TARGET; + } + + "Output: %s\n".printf: @circ-primes[ 0 .. $TARGET - 1 ].join: ', '; +} + +#------------------------------------------------------------------------------ +sub find-circular-primes( UInt:D $num --> Array:D[UInt:D] ) +#------------------------------------------------------------------------------ +{ + my UInt @circ-primes; + my UInt @perms = get-permutations( $num ); + + while @perms + { + my UInt $perm = @perms.shift; + my UInt @cycle = get-cycle( $perm ); + my Bool $all-prime = True; + + for @cycle -> UInt $n + { + unless $n.is-prime + { + $all-prime = False; + last; + } + } + + @circ-primes.push: $perm if $all-prime; + + for @cycle -> UInt $n + { + my UInt $i = @perms.first: * == $n, :k; + + @perms.splice: $i, 1 if $i.defined; + } + } + + return @circ-primes; +} + +#------------------------------------------------------------------------------ +# E.g., 137 -> 713 -> 371 [-> 137] +# +sub get-cycle( UInt:D $first --> Array:D[UInt:D] ) +#------------------------------------------------------------------------------ +{ + my UInt @cycle = $first; + my UInt @digits = $first.split( '', :skip-empty ).map: { .Int }; + my UInt $next; + + repeat + { + my $last = @digits.pop; + @digits = $last, |@digits; + $next = @digits.join( '' ).Int; + + @cycle.push: $next; + + } until $next == $first; + + @cycle.pop; + + return @cycle; +} + +#------------------------------------------------------------------------------ +sub get-permutations( UInt:D $n --> Array:D[UInt:D] ) +#------------------------------------------------------------------------------ +{ + # Note: the digits are already sorted (ascending) + + my UInt @digits = $n.split( '', :skip-empty ).map: { .Int }; + + my Array[UInt] @perms = Array[Array[UInt]].new: + @digits.permutations.map: { Array[UInt].new: $_ }; + + my UInt %perm{UInt}; + + ++%perm{ .join.Int } for @perms; + + return Array[UInt].new: %perm.keys.sort; +} + +#------------------------------------------------------------------------------ +sub get-combinations( --> Array:D[UInt:D] ) +#------------------------------------------------------------------------------ +{ + my UInt @combs; + + for @DIGITS -> UInt $a + { + for @DIGITS -> UInt $b + { + next if $b < $a; + + for @DIGITS -> UInt $c + { + next if $c < $b; + + @combs.push: "$a$b$c".Int; + + for @DIGITS -> UInt $d + { + next if $d < $c; + + @combs.push: "$a$b$c$d".Int; + + for @DIGITS -> UInt $e + { + next if $e < $d; + + @combs.push: "$a$b$c$d$e".Int; + + for @DIGITS -> UInt $f + { + next if $f < $e; + + @combs.push: "$a$b$c$d$e$f".Int; + } + } + } + } + } + } + + return @combs; +} + +#------------------------------------------------------------------------------ +# Remove 333, 777, 999, 3333, ..., but NOT 111, 1111, ... +# +sub filter-reps( Array:D[UInt:D] $combs --> Array:D[UInt:D] ) +#------------------------------------------------------------------------------ +{ + my UInt @filtered; + + for @$combs -> $comb + { + my UInt @digits = $comb.split( '', :skip-empty ).map: { .Int }; + my UInt %count; + + ++%count{ $_ } for @digits; + + if %count{ 1 }:exists || %count.keys.elems > 1 + { + @filtered.push: $comb; + } + } + + @filtered.= sort: { .chars, .Int }; + + return @filtered; +} + +#------------------------------------------------------------------------------ +sub USAGE() +#------------------------------------------------------------------------------ +{ + my Str $usage = $*USAGE; + + $usage ~~ s/ ($*PROGRAM-NAME) /raku $0/; + + $usage.put; +} + +############################################################################### diff --git a/challenge-167/athanasius/raku/ch-2.raku b/challenge-167/athanasius/raku/ch-2.raku new file mode 100644 index 0000000000..96d9738464 --- /dev/null +++ b/challenge-167/athanasius/raku/ch-2.raku @@ -0,0 +1,296 @@ +use v6d; + +############################################################################### +=begin comment + +Perl Weekly Challenge 167 +========================= + +TASK #2 +------- +*Gamma Function* + +Submitted by: Mohammad S Anwar + +Implement subroutine gamma() using the [ https://en.wikipedia.org/wiki/Lanczos_ +approximation |Lanczos approximation] method. + +[2022-05-31] + Ryan Thompson wrote an interesting blog explaining the subject in details. + Highly recommended if you are looking for more information. [ https://ry.ca/ + 2022/ 05/lanczos-approximation |BLOG]. + +Example + + print gamma(3); # 1.99 + print gamma(5); # 24 + print gamma(7); # 719.99 + +=end comment +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=begin comment + +Command-Line Interface +---------------------- +2 arguments: Real and imaginary parts of the complex argument to the gamma + function +1 argument: Real part as above; the imaginary part defaults to zero +0 arguments: Run the test suite (expected values obtained from [4] and [2]). + +Note: If the first argument (the real part) is negative, its minus sign will be +interpreted as a command-line flag. To prevent this, it is necessary to precede +the first argument with a double dash: + + raku ch-2.raku -- -0.5 + +Algorithm +--------- +A port from Python to Perl of the "Simple implementation" in [3]. Note: I have +departed from the Task specification by implementing a separate factorial +calculation for positive integers. This gives more accurate results: e.g., +gamma(3) = 2 (which is exactly correct) rather than 1.99 as in the Example. + +References +---------- +[1] "Gamma function", Wikipedia, https://en.wikipedia.org/wiki/Gamma_function +[2] "Gamma function Calculator", ke!san Online Calculator, + https://keisan.casio.com/exec/system/1180573444 +[3] "Lanczos approximation", Wikipedia, https://en.wikipedia.org/wiki/Lanczos_ + approximation +[4] "Particular values of the gamma function", Wikipedia, + https://en.wikipedia.org/wiki/Particular_values_of_the_gamma_function + +=end comment +#============================================================================== + +my UInt constant $ACCURACY = 13; # Significant digits +my Real constant $EPSILON = 1e-12; +my Real constant $INIT-X = 0.99999999999980993; +my constant @P-COEFS = 676.5203681218851, + -1259.1392167224028, + 771.32342877765313, + -176.61502916214059, + 12.507343278686905, + -0.13857109526572012, + 9.9843695780195716e-6, + 1.5056327351493116e-7; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + "\nChallenge 167, Task #2: Gamma Function (Raku)\n".put; +} + +#============================================================================== +sub MAIN +( + Real $re?, #= Real part + Real $im = 0, #= Imaginary part +) +#============================================================================== +{ + if $re.defined + { + my Complex $z = Complex.new: $re, $im; + + "gamma($z) = { gamma( $z ) }".put; + } + else + { + test(); + } +} + +#------------------------------------------------------------------------------ +sub gamma( Complex:D $z is rw --> Complex:D ) +#------------------------------------------------------------------------------ +{ + my Complex $y = Complex.new: NaN, 0; # Assume failure + + if is-integer( $z ) + { + if $z.re > 0 + { + # Calculate the factorial directly, for better accuracy + |
