ETOOBUSY 🚀 minimal blogging for the impatient
PWC155 - Fortunate Numbers
TL;DR
Here we are with TASK #1 from The Weekly Challenge #155. Enjoy!
The challenge
Write a script to produce first
8 Fortunate Numbers
(unique and sorted).According to Wikipedia
A Fortunate number, named after Reo Fortune, is the smallest integer m > 1 such that, for a given positive integer n, pn# + m is a prime number, where the primorial pn# is the product of the first n prime numbers.
Expected Output
3, 5, 7, 13, 17, 19, 23, 37
The questions
None. From a generalization point of view, it would be interesting to know what a limit would be instead of 8.
The solution
The most tricky part for me was that the challenge is about finding the first Fortunate numbers, unique and sorted.
So it’s not simply about finding the Fortunate numbers associated to the first $n$ primes, but to sort them and take the first $n$ out of all possible.
Luckily, it’s possible to state that there is a lower limit for values of Fortunate numbers and this limit strictly grows with $n$. Hence, it’s possible to draw a line beyond which the first $m$ Fortunate numbers (sorted) are also the right ones, so to say.
In particular, this lower limit for the $n$th Fortunate number is the $n$th prime number. Easy huh?
Let’s start with Raku:
#!/usr/bin/env raku
sub MAIN (Int:D $n = 8) {
first-fortunate-numbers($n).join(', ').put;
return 0;
}
sub first-fortunate-numbers($n) {
my &it = fortunate-numbers-it();
my @cleared;
my @seen;
while @cleared < $n {
my ($prime, $fn) = &it();
@seen = (@seen.Slip, $fn).sort.unique;
@cleared.push: @seen.shift while @seen && @seen[0] < $prime;
}
return @cleared[^$n];
}
sub fortunate-numbers-it() {
my $primorial = 1;
my &pit = primes-it();
return sub {
my $prime = &pit(); # get next prime
$primorial *= $prime; # update the primorial
return 2, 3 if $prime == 2;
my $n = $prime;
loop {
$n += 2;
return $prime, $n if ($primorial + $n).is-prime;
}
}
}
sub primes-it() {
my @cache = 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47;
my $last;
return sub {
return $last = @cache.shift if @cache;
loop {
$last += 2;
return $last if $last.is-prime;
}
}
}
We build an iterator for prime numbers primes-it()
and an iterator
for Fortunate numbers fortunate-numbers-it()
. This latter one returns
two items for each call, namely the $n$th prime number and its
associated Fortunate number.
This allows us apply a filtering algorithm in
first-fortunate-numbers()
where we keep new entries in array @seen
,
moving items into array @cleared
as we move on the lower limit and
thus clear the lower found values (i.e. there will not be any new
Fortunate number below them). We iterate until we have enough cleared
elements, then return the needed amount.
The Perl translation leverages List::MoreUtils and the venerable ntheory, to implement what’s basically a blunt translation:
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
use FindBin '$Bin';
use lib "$Bin/local/lib/perl5";
use ntheory qw< is_prime next_prime >;
use List::MoreUtils 'uniq';
use bigint;
say join ', ', first_fortunate_numbers(shift || 8);
sub first_fortunate_numbers ($n) {
my $it = fortunate_numbers_it();
my (@cleared, @seen);
while (@cleared < $n) {
my ($prime, $fn) = $it->();
@seen = uniq sort { $a <=> $b } (@seen, $fn);
push @cleared, shift @seen while @seen && $seen[0] < $prime;
}
return @cleared[0 .. $n - 1];
}
sub fortunate_numbers_it {
my $primorial = 1;
my $prime = 1; # bear with me please...
return sub {
$prime = next_prime($prime);
$primorial *= $prime;
return (2, 3) if $prime == 2;
my $n = $prime;
while ('necessary') {
$n += 2;
return ($prime, $n) if is_prime($primorial + $n);
}
};
}
I hope I didn’t miss any corner case… stay safe anyway!