Cuban Addition, Primarily
with Raku

by Arne Sommer

Cuban Addition, Primarily with Raku

[177] Published 2. April 2022. Updated 5. April 2022.

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

Challenge #158.1: Additive Primes

Write a script to find out all Additive Primes <= 100.

Additive primes are prime numbers for which the sum of their decimal digits are also primes.

Output:
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89

The highest value restraint is a new twist. We have been tasked to print a specifc number of values before - and that is easy. This challenge is similar to the Pernicious Numbers (see Weirdly Pernicious with Raku), if we just want the sequence:

File: additive-primes-seq
#! /usr/bin/env raku

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

my $ap := (1..Inf).grep( *.is-prime ).grep( *.comb.sum.is-prime ); # [2]

$ap.head($length).join(", ").say;

[1] No upper limit, but the number of values to print.

[2] The numner itself must be a prime (the first grep), as must the sum of the digits (the second grep).

Running it:

$ ./additive-primes-seq
2, 3, 5, 7, 11, 23, 29, 41, 43, 47

$ ./additive-primes-seq 19
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89, 101, 113, 131, 137, 139

$ ./additive-primes-seq 14
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89

Looking good.

Let us have a go at optimizing the expression. Daisy chaining grep twice is silly.

First the original:

> (1..Inf).grep( *.is-prime ).grep( *.comb.sum.is-prime ).head(14)
(2 3 5 7 11 23 29 41 43 47 61 67 83 89)

Then we merge them with &&:

> (1..Inf).grep( *.is-prime && *.comb.sum.is-prime ).head(14)
(2 3 5 7 11 12 14 16 20 21 23 25 29 30)

Oops!

The first part (the left hand side of &&) does not matter, as we get the exact same result without it:

> (1..Inf).grep( *.comb.sum.is-prime ).head(14)
(2 3 5 7 11 12 14 16 20 21 23 25 29 30)

This one is pretty absurd, as it would seem to imply that the values are primes and not primes - at the same time:

>  (1..Inf).grep( *.is-prime.not && *.is-prime ).head(14)
(2 3 5 7 11 13 17 19 23 29 31 37 41 43)

Whatever is Going On?

Actually, it is. (Whatever, that is.)

The problem is that * (the first star) and * (the second star) are not the same thing(y). The Fibonacci Sequence set up with two of those Whatever stars is illuminating:

my @f = 0, 1, * + * ... *;

The expression must be read from the right, to avoid headache. The rightmost star is the same is Infinity. The next one (to the left) refers to the previous value in the sequence, and the leftmost one refers to the value before that again.

See docs.raku.org/type/Whatever for more information about the Whatever class (and star placeholder).

The grep block is supplied with one argument for each iteration (as it is a loop in disguise). The first star does not have anything to refer to, and for some reason (that is beyond me) that part is simply ignored.

Using a closure (the { and } brackets) does the trick:

> (1..Inf).grep( { .is-prime && .comb.sum.is-prime } ).head(14)
(2 3 5 7 11 23 29 41 43 47 61 67 83 89)

Then we can have a go at the values <= 100, as specified in the challenge:

File: additive-primes
#! /usr/bin/env raku

unit sub MAIN (UInt $limit = 100);

my $ap := (1..Inf).grep( { .is-prime && .comb.sum.is-prime } );

my @ap;

loop
{
  state $index = 0;              # [1]
  my $current  = $ap[$index++];
  last if $current > $limit;
  @ap.push: $current;
}
  
@ap.join(", ").say;

[1] I have chosen to iterate (with an index) over the sequence. Using a state variable (declared with state) makes it possible to set it up inside the loop, so that it is not visible outside.

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

Running it:

$ ./additive-primes
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89

$ ./additive-primes 110
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89, 101

$ ./additive-primes 47
2, 3, 5, 7, 11, 23, 29, 41, 43, 47

Looking good.

5. April Update

As zeekar pointed out in Reddit, we can (and indeed should) specify the upper limit in the starting range. That will give a much simpler program:

File: additive-primes2
#! /usr/bin/env raku

unit sub MAIN (UInt $limit = 100);

say (1 ..^ $limit).grep({ .is-prime && .comb.sum.is-prime }).join(", ");

Running it gives the expected result:

$ ./additive-primes2
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89

$ ./additive-primes2 200
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89, 101, 113, 131, 137, \
  139, 151, 157, 173, 179, 191, 193, 197, 199

A Perl Version

This is straight forward translation of the Raku versionm using a (one) traditional loop.

File: additive-primes-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use feature 'state';
use Math::Prime::Util 'is_prime';
use List::Util 'sum';

my $limit = $ARGV[0] || 100;

my @ap;

while (1)
{
  state $current = 0;

  last if $current++ >= $limit;
  next unless is_prime($current);

  my @digits = split(//, $current);
  my $sum    = sum(@digits);

  push(@ap, $current) if is_prime($sum);
}

say join(", ", @ap);

Running it gives the expected result:

$ ./additive-primes-perl
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89

$ ./additive-primes-perl 200
2, 3, 5, 7, 11, 23, 29, 41, 43, 47, 61, 67, 83, 89, 101, 113, 131, 137,\
  139, 151, 157, 173, 179, 191, 193, 197, 199

Challenge #158.2: First Series Cuban Primes

Write a script to compute first series Cuban Primes <= 1000. Please refer wikipedia page for more informations.

Output:
7, 19, 37, 61, 127, 271, 331, 397, 547, 631, 919.

As a sequence first, just as for the «Additive Primes»:

File: fscp-seq
#! /usr/bin/env raku

unit sub MAIN (UInt $length = 10);

my $fscp := (1..Inf).map( { 3 * $_ ** 2 + 3 * $_ + 1 } ).grep( *.is-prime );

$fscp.head($length).join(", ").say;

The expression is straight out of the wikipedia page.

Running it:

$ ./fscp-seq
7, 19, 37, 61, 127, 271, 331, 397, 547, 631

$ ./fscp-seq 20
7, 19, 37, 61, 127, 271, 331, 397, 547, 631, 919, 1657, 1801, 1951, 2269,\
  2437, 2791, 3169, 3571, 4219

Looking good.

Adding an upper limit, instead of an element count, is easy. The additional code has been copied from «additive-primes»:

File: fscp
#! /usr/bin/env raku

unit sub MAIN (UInt $limit = 1000);

my $fscp := (1..Inf).map( { 3 * $_ ** 2 + 3 * $_ + 1 } ).grep( *.is-prime );

my @fscp;

loop
{
  state $index = 0;
  my $current  = $fscp[$index++];
  last if $current > $limit;
  @fscp.push: $current;
}
  
@fscp.join(", ").say;

Running it:

$ ./fscp 
7, 19, 37, 61, 127, 271, 331, 397, 547, 631, 919

$ ./fscp 2000
7, 19, 37, 61, 127, 271, 331, 397, 547, 631, 919, 1657, 1801, 1951

Looking good.

Note that I have dropped the trailing period (.) that was specified in the challnge output, as the first part did not have it.

A Perl Version

This is straight forward translation of the Raku versionm using a (one) traditional loop.

File: fscp-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use feature 'state';
use Math::Prime::Util 'is_prime';
use List::Util 'sum';

my $limit = $ARGV[0] || 1000;

my @fscp;

while (1)
{
  state $current = 0;

  my $mapped = 3 * $current ** 2 + 3 * $current++ + 1;

  last if $mapped >= $limit;

  push(@fscp, $mapped) if is_prime($mapped);
}

say join(", ", @fscp);

Running it gives the expected result:

$ ./fscp-perl
7, 19, 37, 61, 127, 271, 331, 397, 547, 631, 919

And that's it.