Primarily Functional
with Raku

by Arne Sommer

Primarily Functional with Raku

[186] Published 5. June 2022.

This is my response to the Perl Weekly Challenge #167.

Challenge #167.1: Circular Prime

Write a script to find out first 10 circular primes having at least 3 digits (base 10).

Please checkout 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.

0utput:
113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933

113 is a circular prime because it is a prime, as is 131 and 311 (the «cyclically permuting» versions). Note that 131 and 311 are not part of the sequence, as they are permutations of the original one (with the lowest value).

File: circular-prime
#! /usr/bin/env raku

unit sub MAIN (Int $c where $c > 1 = 10);           # [1]

my $cp := (100 .. *).grep({ is-circular($_) });     # [2]

sub is-circular ($number)                           # [4]    
{
  state %seen;                                      # [5]

  return if %seen{$number}; %seen{$number} = True;  # [5]

  return unless $number ~~ /^<[1379]>+$/;           # [6]
  return unless $number.is-prime;                   # [7]

  my @digits = $number.comb;                        # [8]

  for (1 .. @digits.elems -1) -> $skew              # [9]
  {
    my $rotated = @digits.rotate($skew).join;       # [10]
    return if %seen{$rotated};                      # [11]
    return unless $rotated.is-prime;                # [12]
  }

  return True;                                      # [13]
}

say $cp[^$c].join(", ");                            # [3]

[1] The number of circular primes to print, if the default 10 does not suit you.

[2] A sequence of the circular primes, using a cutsom procedure to sort out the member values.

[3] Print the first $c values from the sequence, with commas between them.

[4] Is the current number a circular prime?.

[5] Using a state variable to store the visited values (i.e. 113, so that we can get rid of 131 and 311 later on).

See docs.raku.org/syntax/state for more information about the variable declarator state.

[6] The number cannot have any other digits that 1, 3, 7 and 9 (any amount of them).

[7] It has to be a prime.

[8] Get the individual digits (as rotation is easier on a list than on a string).

[9] As many times as necessary to get all the rotational variants of the number.

[10] Do the rotation (with rotate).

See docs.raku.org/routine/rotate for more information about rotate

[11] Return (with an implicit False) if we have seen the number before (as described in [5]).

[12] Return (with an implicit False) if it is not a prime.

[13] It did not fail, tahn we have succeeded - and it is a circular prime.

Running it:

$ ./circular-prime
113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933

$ ./circular-prime 6
113, 197, 199, 337, 1193, 3779

We should, perhaps, show this very fast version:

File: circular-prime-stupid
#! /usr/bin/env raku

say "113, 197, 199, 337, 1193, 3779, 11939, 19937, 193939, 199933";

Does it satisfy the «find out» clause?

Probably not.

(But it is easy to translate this program to virtually any other programming language.)

Challenge #167.2: Gamma Function

Implement subroutine gamma() using the Lanczos approximation method.

Example:
print gamma(3); # 1.99
print gamma(5); # 24
print gamma(7); # 719.99

We do not actually have to implement anything, as the GNU Scientific Library has done so for us. This library is available in Raku as «Math::Libgsl::Function», using the Nativecall interface.

File: gamma-function
#! /usr/bin/env raku

use Math::Libgsl::Function :ALL;                       # [1]

unit sub MAIN (Int :u(:$upto) where $upto > 0 = 10);   # [2]

say "$_ -> { gamma($_) }" for 1 .. $upto;              # [3]

[1] Load the library, and import everything (:ALL).

[2] Print the values from 1 up to 10, unless another upto value is specified.

[3] GNU does the job for us.

Running it:

$ ./gamma-function
1 -> 1
2 -> 1
3 -> 2
4 -> 6
5 -> 24
6 -> 120
7 -> 720
8 -> 5040
9 -> 40320
10 -> 362880

We did not get the exact same result as specified in the challenge (for 3 and 7), but I assume that Raku is correct - as it has much better rounding error prevention than Perl.

$ ./gamma-function -u=20
1 -> 1
2 -> 1
3 -> 2
4 -> 6
5 -> 24
6 -> 120
7 -> 720
8 -> 5040
9 -> 40320
10 -> 362880
11 -> 3628800
12 -> 39916800
13 -> 479001600
14 -> 6227020800
15 -> 87178291200
16 -> 1307674368000
17 -> 20922789888000
18 -> 355687428096000
19 -> 6.402373705728e+15
20 -> 1.21645100408832e+17

Very large values do give rounding errors (using the e+ Scientific notation), but we can avoid that by telling Raku to stick with integers.

File: gamma-function-int
#! /usr/bin/env raku

use Math::Libgsl::Function :ALL;

unit sub MAIN (Int :u(:$upto) where $upto > 0 = 10);

say "$_ -> { gamma($_).Int }" for 1 .. $upto;  # [1]

[1] Coerce the value to integer with .Int.

See docs.raku.org/routine/Int for more information about the Int coercer.

Running it:

$ ./gamma-function-int -u=20
1 -> 1
2 -> 1
3 -> 2
4 -> 6
5 -> 24
6 -> 120
7 -> 720
8 -> 5040
9 -> 40320
10 -> 362880
11 -> 3628800
12 -> 39916800
13 -> 479001600
14 -> 6227020800
15 -> 87178291200
16 -> 1307674368000
17 -> 20922789888000
18 -> 355687428096000
19 -> 6402373705728000
20 -> 121645100408832000

And that's it.