The Weekly Challenge ‐ Perl and Raku

CY's Take on The Weekly Challenge #155

If you want to challenge yourself on programming, especially on Perl and/or Raku, go to https://theweeklychallenge.org, code the latest challenges, submit codes on-time (by GitHub or email).

Do tell me, if I am wrong or you strongly oppose my statements!

It's time for challenges in Week #155 !

Two number theory tasks.


Task 1: Fortunate Numbers

(From Wikipedia) One of unsolved problems in mathematics:
Fortune's conjecture: Are any Fortunate numbers composite?

I have never thought that things like primorial can go with such an interesting and natural conjecture.

Back to our task. Looking at the wikipedia page linked from the task statement, we know that there are TWO sequences of Fortunate numbers:

The first one is a1(n) is the smallest integer m so that m + primorial(n) is prime.

The second one is sort {$a<=>$b} uniqint a1 (or equivalently, uniqint sort {$a<=>$b} a1 [1]).

The task requires us to generate the second sequence.

 

A concern is, a1(x) can be smaller than a1(y) when x > y. So how do we know when to stop generating terms of a1?

Keep reading on the Wikipedia gives us a hint: "The Fortunate number for primorial(n) is always above pn."

So after we get a length-$N list of non-duplicate values of a1 we grasp its largest element $E, and check whether there are Fortunate numbers smaller than $E. The check will be stopped when we reach j, where j is determined from the inequality pj$E < pj+1 .

 

The worry has been cleared and we should start to code:

The first part, to generate a1 up to a certain "large-enough" value:

# first part

{
    my $k = scalar @primorials - 1;
    my $tmp_int = $primes[$k];
    do {
        $tmp_int++;
    } while (!is_prime( $primorials[-1] + $tmp_int ));
    push @fort_unsort, $tmp_int;
    iter_primorial();    # generate the next primorial
    @fort_sorted = uniqint sort {$a<=>$b} @fort_unsort;
}

Terms in @primorials grow fast and I set them to be Math::BigInt objects. This is the reason that I generate it one-by-one ‐ avoid eating up memory.

The second part, to check whether there are any small Fortunate numbers not yet included in @fort_unsort:

# second part

{
    # ... 
    my $k = scalar @primorials - 1;
    my $tmp_int = $primes[$k];
    do {
        $tmp_int++;
    } while (!is_prime( $primorials[-1] + $tmp_int ));
    push @fort_unsort, $tmp_int;
    @fort_sorted = uniqint sort {$a<=>$b} @fort_unsort;
}

I try to keep the size of @primes and @primorials same. And at certain moment I felt the name of @primes confusing, because it is not a full list of primes. Then I changed it into @ch_primes.

Here I reveal the loop conditional for the second part:

while ($fort_sorted[$N-1] > $ch_primes[-1])

The loop conditional for the first part, actually, can be quite arbitary. Each time the block runs, @fort_sorted is either increased by 1 or remain the same. One may casually use while (scalar @fort_sorted != 2*$N ) or while (scalar @fort_sorted < 2*$N ) , and s/he could even skip the second part ... IF LUCKY ENOUGH?

To go on a steady pace, I use

while (scalar @fort_sorted < $N) 

Since the first part and the second part look so similar, I combine them by a short-circuit or; here is the code:

my $N = $ARGV[0] || 8;

my @ch_primes = (2);
my @primorials = ( Math::BigInt->new(2) );
my @fort_unsort = ();
my @fort_sorted = ();


while ( !defined($fort_sorted[$N-1])
               ||
        $fort_sorted[$N-1] > $ch_primes[-1]
) {
    my $k = scalar @fort_unsort;
    my $tmp_int = $ch_primes[$k];
    do {
        $tmp_int++;
    } while (!is_prime( $primorials[$k] + $tmp_int ));
    push @fort_unsort, $tmp_int;
    iter_primorial() while scalar @primorials <= scalar @fort_unsort;
    iter_prime() while scalar @ch_primes <= scalar @primorials;
    @fort_sorted = uniqint sort {$a<=>$b} @fort_unsort;
}

say "Answer: \n", join ", ", @fort_sorted[0..$N-1];

while the supporting subroutines &iter_prime and &iter_primorial are

sub iter_prime {
    my $new_int = $ch_primes[-1];
    do {
        $new_int++;
    } while (!is_prime($new_int));
    push @ch_primes, $new_int;
}

sub iter_primorial {
    iter_prime() while scalar @ch_primes <= scalar @primorials; 
    push @primorials,
      $primorials[-1]*$ch_primes[ scalar @primorials ];
}

Sample run:

$ perl ch-1.pl 10
Answer: 
3, 5, 7, 13, 17, 19, 23, 37, 47, 59

---

Is the heuristic while (scalar @fort_sorted < 2*$N ) good? Let add a line say scalar @fort_unsort; and see:

$N scalar @fort_unsort
8 10
16 24
38 63
50 84
101 183
150 272

---

I have drawn an alternative version of the code after reading polettix's blogpost on the task. We need not store the values of many primorials; we can just store one and proceed.

my @ch_primes = (2);
my $my_primorial = Math::BigInt->new(2);
my $ind_primorial = 1;
my @fort_unsort = ();
my @fort_sorted = ();


while ( !defined($fort_sorted[$N-1])
               ||
        $fort_sorted[$N-1] > $ch_primes[-1]
) {
    my $k = scalar @fort_unsort;
    my $tmp_int = $ch_primes[$k];
    do {
        $tmp_int++;
    } while (!is_prime( $my_primorial + $tmp_int ));
    push @fort_unsort, $tmp_int;
    iter_primorial() while $ind_primorial <= scalar @fort_unsort;
    iter_prime() while scalar @ch_primes <= $ind_primorial;
    @fort_sorted = uniqint sort {$a<=>$b} @fort_unsort;
}

say "Answer: \n", join ", ", @fort_sorted[0..$N-1];


sub iter_prime {
    my $new_int = $ch_primes[-1];
    do {
        $new_int++;
    } while (!is_prime($new_int));
    push @ch_primes, $new_int;
}

sub iter_primorial {
    iter_prime() while scalar @ch_primes <= $ind_primorial; 
    $my_primorial *= $ch_primes[ $ind_primorial ];
    $ind_primorial++;
}

An anecdote: we never assume the Fortunate conjecture true all along the code process, though, it's quite cool.

Task 2: Pisano Period

As usual I try to generalize. This time I generalize it into arbitary length of "order" of linear recurrence.

https://oeis.org/wiki/Index_to_OEIS:_Section_Rec

ch-2.pl $N 2   1 1       0 1   # Fibonacci numbers

ch-2.pl $N 2   1 1       2 1   # Lucas numbers    #OEIS:A000032

ch-2.pl $N 2   1 2       0 1   # Pell numbers     #OEIS:A000129

ch-2.pl $N 3   1 1 0  1 1 1 # Padovan numbers     #OEIS:A000931

ch-2.pl $N 3   1 1 1  0 0 1 # Tribonacci numbers  #OEIS:A000073

ch-2.pl $N 3   1 0 1  1 1 1 # Narayana's cows sequence #A000930

ch-2.pl $N 6  -1 -1 0 -1 2 1   1 2 2 4 5 9             #A001224

ch-2.pl $N 6   1 -1 -1 0 1 1   1 1 2 3 4 5             #A001399

So, how does the script work?

  1 sub pisano_period {
  2     my ($N, $t, $rec, $seq) = @_;
  3     die "(Some of) Parameters are too large.\n"
  4         if $N**$t + $t - 1 > 8_000_000;
  5 
  6     @$seq = map {$_ % $N} @$seq;
  7     my $ori_seqstate = [@$seq];
  8     my $new_seqstate = [@$ori_seqstate];
  9     my $count = 0;
 10     do {
 11         my $new_val = sum map {$rec->[$_]*$new_seqstate->[$_]} (0..$t-1);
 12         $new_val = $new_val % $N;
 13         push @{$seq}, $new_val;
 14         shift @{$new_seqstate};
 15         push @{$new_seqstate}, $new_val;
 16         $count++;
 17         die "Patterns not found\n" if $count > $N**$t + $t;
 18     } while (!cmp_num_arr($new_seqstate, $ori_seqstate));
 19 
 20     return [@$seq[0..$count-1]];
 21 }

line 02: Let's explain it using examples.

For the input ch-2.pl 7 3   1 0 1   1 1 1, the desired output is the info of the periodic behavior of the Narayana's cows sequence modulo 7.

$rec will be [1,0,1] (the sequence is defined by a(n-1)+a(n-3) for n ≥ 3) and $ori_seqstate will be [1,1,1] (a(0)=a(1)=a(2)=1).

For the input ch-2.pl 17 2   1 2   0 1, the desired output is the info of the periodic behavior of the Pell numbers modulo 17.

$rec will be [1,2] (the sequence is defined by 2 × a(n-1)+a(n-2) for n ≥ 2) and $ori_seqstate will be [0,1] (a(0)=0, a(1)=1).

line 03-04, line 17: $N**$t + $t - 1 is a lazy upper bound. By the pigeonhole principle in discrete math, there are $N**$t possible length-$t-sequence (while the possible values of members of the length-$t-sequence are 0 to $N-1).

line 11: I am curious if there is a more compact way to write it. Note that $rec->@* and $new_seqstate->@* have same number of elements.

line 14-15: They can be replaced by $new_seqstate = [ @$new_seqstate[1..$t-1] , $new_val ]; .

line 18: &cmp_num_arr is a rough supportive subroutine checking whether two lists are the same.

line 17: I have to admit that I have given up. Depending on the initial condition, $ori_seqstate may appear once; for instance, for a recurrence sequence specified by a(0)=a(1)=1 and a(n) = 4 a(n-1) + 4 a(n-2), then the code here will return a "Patterns not found" message for many $N. I have not done the details math of the necessary and sufficient condition for a linear recurrence sequence which surely generates a Pisano-like period.

Sample run:

$ perl ch-2.pl 16 2  1 1  0 1 # Fibonacci numbers 
1..4
length: 24
terms: 0, 1, 1, 2, 3, 5, 8, 13, 5, 2, 7, 9, 0, 9, 9, 2, 11, 13, 8, 5, 13, 2, 15, 1
ok 1 - Required Case (Period of Fibonacci numbers mod 3)
ok 2 - Pisano period for mod 2
ok 3 - Pisano-like period for Padovan numbers mod 3
ok 4 - Pisano-like period for Padovan numbers mod 7

Anyway, have fun exploring the periodic behavior of linear recurrence sequences.

$N Sequence Period Length
2 Fibonacci 3
3 Fibonacci 8
7 Fibonacci 16
101 Fibonacci 50
191 Fibonacci 190
2 Padovan 7
3 Padovan 13
7 Padovan 48
101 Padovan 100
191 Padovan 7296
2 Tribonacci 4
3 Tribonacci 13
7 Tribonacci 48
43 Tribonacci 308
101 Tribonacci 680
191 Tribonacci 36673
193 Tribonacci 4656
197 Tribonacci 3234
199 Tribonacci 198
2 Narayana's cow 7
3 Narayana's cow 8
7 Narayana's cow 57
53 Narayana's cow 468
101 Narayana's cow 10303
191 Narayana's cow 36673
193 Narayana's cow 37443
197 Narayana's cow 2156
199 Narayana's cow 3960
2 OEIS:A001224 12
3 OEIS:A001224 16
7 OEIS:A001224 32
11 OEIS:A001224 20
13 OEIS:A001224 56
2 OEIS:A001399 12
3 OEIS:A001399 18
4 OEIS:A001399 24
5 OEIS:A001399 30
7 OEIS:A001399 42
11 OEIS:A001399 66
13 OEIS:A001399 78

Well, there are many fun discussions on the properties of Fibonacci sequences on Mathematics Stack Exchange, some related to the Pisano Period.

Unfortunately, I have been infected by COVID-19 Omicron this week ‐ tested with rapid tests. Three shots of vaccine were injected before. And three exams in April. I better speak less and take more rest. Everyone take care.

Stay alert and healthy everyone! Hope for peace and justice on East Europe! □


Remarks:

1: From my testing up to the highest member of the sequence is 1103, the performance between the different order of occurring of the two array operations seems to be very close.

Except from images and codes from other personnels, the content of this blogpost is released under a copyleft spirit. One may share (full or partial) content of this blogpost on other platform if you share it under the free and open content spirit.

link for CY's full codes: ch-1.pl, ch-2.pl


Contact on twitter: @e7_87.

Discuss via GitHub issues: here.

Email: fungcheokyin at gmail.com

Created Date: 10th March, 2022; typos and grammars fixed on 14th March.