Perl Weekly Challenge 246: Linear Recurrence of Second Order

These are some answers to the Week 246, Task 2, of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (on December 10, 2023 at 23:59). This blog post provides some solutions to this challenge. Please don’t read on if you intend to complete the challenge on your own.

Task 1: Linear Recurrence of Second Order

You are given an array @a of five integers.

Write a script to decide whether the given integers form a linear recurrence of second order with integer factors.

A linear recurrence of second order has the form

a[n] = p * a[n-2] + q * a[n-1] with n > 1

where p and q must be integers.

Example 1

Input: @a = (1, 1, 2, 3, 5)
Output: true

@a is the initial part of the Fibonacci sequence a[n] = a[n-2] + a[n-1]
with a[0] = 1 and a[1] = 1.

Example 2

Input: @a = (4, 2, 4, 5, 7)
Output: false

a[1] and a[2] are even. Any linear combination of two even numbers with integer factors is even, too.
Because a[3] is odd, the given numbers cannot form a linear recurrence of second order with integer factors.

Example 3

Input: @a = (4, 1, 2, -3, 8)
Output: true

a[n] = a[n-2] - 2 * a[n-1]

Finding whether:

a[n] = p * a[n-2] + q * a[n-1]

isn't very difficult, except that p and q can both take an infinite number of values. We need to limit the range of these two integers. I initially wrote a Raku program in which p and q iterated over hard-coded integers from -5 to +5. It was OK for testing (and it worked for the test cases outlined in the task specification), but I did not find that satisfactory. So, I changed the range for p and q to be the minimum and maximum of the input array. This seems to work in all cases, but I can't prove it. Also, there may be a better solution to use a narrower range. But I do not have the energy to investigate the math aspect of it.

Linear Recurrence of Second Order in Raku

We have three nested loops iterating over the values of p and q as described above and over the input array. Note that, in most cases, the most inner loop short-circuits almost immediately when the values of p and q are found to be not suitable for the input array.

sub linear-rec (@in) {
    my @range = @in.minmax;
    for @range -> $p {
        for @range -> $q {
            for 2..@in.end -> $i {
                last if @in[$i] != 
                    $p * @in[$i-2] + $q * @in[$i-1];
                # say "$p $q $i";
                return ("True:  p = $p, q = $q") 
                    if $i == @in.end;
            }
        }
    }
    return (False);
}

my @tests = <1 1 2 3 5>, <4 2 4 5 7>, <4 1 2 -3 8>;
for @tests -> @test {
    printf "%-12s => ", "@test[]";
    say linear-rec @test;
}

This program displays the following output:

$ raku ./linear-recurrence.raku
1 1 2 3 5    => True:  p = 1, q = 1
4 2 4 5 7    => False
4 1 2 -3 8   => True:  p = 1, q = -2

Linear Recurrence of Second Order in Perl

This is a port to Perl of the above Raku program (see the previous sections for explanations).

use strict;
use warnings;
use feature 'say';

sub linear_rec {
    my @in = @_;
    my ($min, $max) = (sort {$a <=> $b} @in)[0, -1];
    for my $p ($min .. $max) {
        for my $q ($min .. $max) {
            for my $i (2..$#in) {
                last if $in[$i] != 
                    $p * $in[$i-2] + $q * $in[$i-1];
                # say "$p $q $i";
                return ("True:  p = $p, q = $q") 
                    if $i == $#in;
            }
        }
    }
    return "False";
}

my @tests = ([<1 1 2 3 5>], [<4 2 4 5 7>], [<4 1 2 -3 8>]);
for my $test (@tests) {
    printf "%-12s => ", "@$test";
    say linear_rec @$test;
}

This program displays the following output:

$ perl linear-recurrence.pl
1 1 2 3 5    => True:  p = 1, q = 1
4 2 4 5 7    => False
4 1 2 -3 8   => True:  p = 1, q = -2

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on December 17, 2023. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.