ETOOBUSY đźš€ minimal blogging for the impatient
PWC147  Truncatable Prime
TL;DR
Here we are with TASK #1 from The Weekly Challenge #147. Enjoy!
The challenge
Write a script to generate first 20 lefttruncatable prime numbers in base 10.
In number theory, a lefttruncatable prime is a prime number which, in a given base, contains no 0, and if the leading
left
digit is successively removed, then all resulting numbers are primes.Example
9137 is one such lefttruncatable prime since 9137, 137, 37 and 7 are all prime numbers.
The questions
I have two questions:

are singledigit prime numbers considered lefttruncatable? I would say no, because there is no â€śresultingâ€ť number after chopping off the first digit. Anyway, my first intuition about what $0!$ should be failed me, so who knows? This will be my working assumption, though.

why stop so low at 20? Weâ€™re not even reaching the example!
While weâ€™re at it, a small personal reflection too.
I enjoy these challenges, in their unique shape of being sometimes quite vague and relying heavily on examples. I think our fine host is a fox disguised as a lamb, throwing us puzzles that sometimes give us that distinct wtf?!? moment that often arises when weâ€™re dealing with customers. Itâ€™s a stimulus to go beyond, ask questions, gather more requirements, try to see things from different perspectives, make assumptionsâ€¦ in summary, architecting a solution.
Thanks manwar.
The solution
OK, letâ€™s get this started. I initially thought about a constructive approach, which ended up with a good amount of code and being sufficiently complicated to leave me with a questionâ€¦ is it even correct?
I mean, I know that the constructive approach (more on this in a minute, promised!), but is my implementation right? No clue.
So I also implemented a dumber, bruteforce exclusive approach based on iterating over all odd values and filtering stuff out if the required condition does not apply. As a comparison. As a test. As a reference.
You know what? In the end, the original constructive approach was correct since the beginning, while the â€śsimplerâ€ť exclusive approach had a bug!
Anyway, I thought of baking the choice between them in the code:
#!/usr/bin/env raku
use v6;
sub MAIN (Int:D $n = 20, :$exclusive = False) {
$exclusive ?? exclusive($n) !! constructive($n);
}
Letâ€™s start with the constructive approach. Weâ€™re aiming for a
nthlefttruncatable($n)
function that will give us the nth number
that applies, so we just have to print all values from the first to the
twentieth:
sub constructive ($n) { put nthlefttruncatable($_) for 1 .. $n }
As a matter of fact, itâ€™s the underlying function that is constructive. The main insight here is that, for a number to be lefttruncatable, the left truncation must either be a singledigit prime number, or it must be lefttruncatable itself.
Hence, itâ€™s easy to find new candidates for lefttruncatable numbers: just start from the ones you have and try adding all possible digits from 1 to 9 in front of them. If we end up with a primeâ€¦ itâ€™s also lefttruncatable.
sub nthlefttruncatable ($nth) {
state @cache = (10..99).grep({ .isprime && .substr(1, 1).isprime });
state $prefix = 1;
state $firstid = 0;
state $nextfirstid = @cache.elems;
state $id = $firstid;
while @cache < $nth { # find moar!
my $candidate = ($prefix ~ @cache[$id++]).Int;
@cache.push($candidate) if $candidate.isprime;
if $candidate.chars == @cache[$id].chars { # toppled over!
if $prefix < 9 {
++$prefix;
}
else {
$prefix = 1;
($firstid, $nextfirstid) = ($nextfirstid, $id);
}
$id = $firstid; # just reset the cursor
}
}
return @cache[$nth  1];
}
The implementation aims at giving something similar to a lazy thing. If
we already have what we need in the @cache
â€¦ well, itâ€™s a good day!
Letâ€™s return it and go take an ice cream. Well, if youâ€™re in Australia,
anyway, otherwise a hot chocolate will do for me, thanks.
Otherwise, we go looking for more leftruncatable numbers until we have
enough to cover the request. To do so, then, we keep some state
variables so that we remember where we stopped in our previous search
and we can restart from there.
The @cache
is prewarmed with all the twodigits lefttruncatable
numbers. This is necessary because weâ€™re excluding singledigit primes
from the lot, so we use the twodigits ones as a starting point.
OK, time for the exclusive approach. Here we have a
islefttruncatable($n)
test function, which tells us whether a given
input $n
is indeed lefttruncatable or just something else. This is
used to print values as we find them, until we hit the limit:
sub exclusive (Int:D $n is copy = 20) {
my $i = 9;
while $n > 0 {
next unless islefttruncatable($i = $i + 2);
$i.put;
$n;
}
}
The implementation actually uses the same insight as before: the characteristic of being lefttruncatable is inherently recursive, so why not?
sub islefttruncatable ($n) {
return False if $n < 10  $n ~~ /0/;
return False unless $n.isprime;
state %cache;
if %cache{$n}:!exists {
my $truncated = $n.substr(1);
return $truncated.isprime if $truncated < 10;
%cache{$n} = islefttruncatable($truncated);
}
return %cache{$n};
}
Just as weâ€™re at it, I decided to throw some %cache
just because
memoization is so cool. Totally overkill and unneeded in this case,
isnâ€™t overengineering so funny?!?
For the Perl counterpart I decided to go only with the constructive alternative. At the end of the day, itâ€™s the one I personally like more, and I already had plenty of references to compare to.
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
say nth_left_truncatable($_) for 1 .. shift // 20;
sub nth_left_truncatable ($nth) {
state $cache = [ grep { is_prime($_) && is_prime(substr $_, 1) } 10 .. 99 ];
state $prefix = 1;
state $first_id = 0;
state $next_first_id = $cache>@*;
state $id = $first_id;
while ($cache>@* < $nth) {
my $candidate = $prefix . $cache>[$id++];
push $cache>@*, $candidate if is_prime($candidate);
if (length($candidate) == length($cache>[$id])) { # toppled over!
if ($prefix < 9) {
++$prefix;
}
else {
$prefix = 1;
($first_id, $next_first_id) = ($next_first_id, $id);
}
$id = $first_id; # just reset the cursor
}
}
return $cache>[$nth  1];
}
sub is_prime { # https://en.wikipedia.org/wiki/Primality_test
return if $_[0] < 2;
return 1 if $_[0] <= 3;
return unless ($_[0] % 2) && ($_[0] % 3);
for (my $i = 6  1; $i * $i <= $_[0]; $i += 6) {
return unless ($_[0] % $i) && ($_[0] % ($i + 2));
}
return 1;
}
Alas, we lack a primality test in the language here, so we have to code one. This is taken from Wikipedia, thereâ€™s probably better stuff around but this does the job in decent time for our purposes.
OK, enough for this postâ€¦ stay safe everybody!