This blog is part of a series dedicated to Fibonacci sequence. In the Series page you can find the other posts of the series.

Just when I thought the spell of the Fibonacci series was gone, it came back to my life. I recently started the so far terrific Coursera course Algorithmic Toolbox that is part of the Data structures and algorithms1. The first advance problem in the second week is as follow.

Problem (Queue length heuristic): Given two integers n and m, output fn mod mf_n \text{ mod } m (that is, the remainder of fnf_n when divided by m).

Input Format. The input consists of two integers n and m given on the same line (separated by a space).

Constraints. 1n10181 \leq n \leq 10^{18}, 2m1052 \leq m \leq 10^5.

Output Format. Output fn mod mf_n \text{ mod } m

Remark (Time limits): language C C++ Java Python C# Haskell JavaScript Ruby Scala time (sec) 1 1 1.5 5 1.5 2 5 5 3
Remark (Memory limit): 512MB.

It took me some time to go through the problem, and because of it and the fact this was another non-trivial example involving Fibonacci sequence, I decided to blog about it2.

I think this problem is meant to show, that sometimes the most efficient algorithms are can only be done if you have some nontrivial insight about the problem. In this case, the problem guide provided as a hint the fact fn mod m f_n \text{ mod } m produces a periodic sequence that repeats itself after a number known as Pisano period annotated as π(m)\pi(m)3. Each repeated cycle always starts with 0, 1 because it is the beginning the first cycle for any fn mod m f_n \text{ mod } m with m>1m > 1. For example, for the case of m = 2 and 3 the sequences are the ones below:

fn mod 2=0,1,1,0,1,1,0,1,1,0,f_n \text{ mod } 2 = 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, …

fn mod 3=0,1,1,2,0,2,2,0,1,1,f_n \text{ mod } 3 = 0, 1, 1, 2, 0, 2, 2, 0, 1, 1, …

So, if for a given modulo mm you know the period π(m)\pi(m) of the sequence fn mod mf_n \text{ mod } m then it follows that

fn mod m=fn mod π(m) mod m f_n \text{ mod } m = f_{n \text{ mod } \pi(m)} \text{ mod } m

This implies that rather computing “fn mod mf_n \text{ mod }m” you can in principle compute “fn mod π(m)mod  mf_{n \text{ mod } \pi(m)} \mod m” that could be significant smaller in complexity4. Therefore, a pseudocode that applies this idea is shown bellow.

Algorithm 1 Algorithm to compute fn mod mf_n \text{ mod } m for large nn

1:procedure PisanoPeriod(mm)

2:previous = 1

3:current = 1

4:for p=2p = 2 do

5:current = FibonacciMod(p,mp, m)

6:if previous == 0 and current == 1 then

7:break

8:end if

9:previous = current

10:end for

11:p = p - 1

12:return p

13:end procedure

14:procedure HugeFibonacciModulo(n,mn,m)

15:if n<2n < 2 then

16:return n

17:end if

18:pp = PisanoPeriod(mm)

19:return FibonacciModulo(n mod p,mn \text{ mod } p,m)

20:end procedure

I hope you can appreciate the ironically tautological nature of the algorithm, that to compute HugeFibonacciModulo you still need to have a somewhat good implementation of FibonacciModulo. This implementation is not only required for the return of the function but more importantly when searching for Pisano period.

The naive approach of computing the Fibonacci number and then taking its modulo it is not efficient. This is because you will need to generate huge numbers before making the modulo, increasing the chances of integer overflow. The trick is, therefore, to see if there is a way to apply modulo in intermediate steps when generating the series. For this, we can explore using the modular addition property5 resulting in that Fibonacci series module mm can be defined by the following recursion

fn mod m=(fn1 mod m+fn2 mod m) mod m for any n>1 f_n \text{ mod } m = (f_{n-1} \text{ mod } m + f_{n-2} \text{ mod } m) \text{ mod } m \text{ for any } n > 1

with initial values f0=0f_0 = 0 and f1=1f_1 = 1 assuming m>1m > 1. The following pseudocode follow implements this recursion.

Algorithm 2 Algorithm to compute fn mod mf_n \text{ mod } m

1:procedure FibonacciModulo(n,mn,m)

2:if n<2n < 2 then

3:return n

4:end if

5:previous = 0

6:current = 1

7:for p=0p = 0 to n1n - 1 do

8:temp = previous

9:previous = current

10:current = (temp + current) mod mm

11:end for

12:return current

13:end procedure

Combining all these procedures you can write a simple version of this algorithm. I will leave it to the reader to complete this assignment.

Does all this work? Well, sort of. This can compute a huge Fibonacci number module mm however, sometimes it might take several seconds to locate the Pisano period when implemented in C++ for some values of mm. This violates the time requirements of the problem, although the algorithm manages to accomplish the task at the most in a reasonable time (tens of seconds).

The reason for the slow down is that the value of Pisano period can wildly change for each mm. The figure below shows a uniform random sample of 5000 values of mm between 22 and 105 10^5 and their corresponding π(m)\pi(m). It is easy to observe that π(m)\pi(m) tend to be higher as mm increases, although there is not a trivial pattern. Therefore, a systematic scanning for a period π(m)\pi(m) given value of modulo mm has a hard to predict execution time, although on average it gets longer for larger numbers.

This means there are two alternatives, either you come up with a smarter algorithm to find the Pisano period by exploiting more profound insights from number theory6 or, you could use a combination of brute force and a lookup table. Of course, I took the higher ground (aka coward way) and created a lookup table by scanning for the Pisano period for module values 2m1052 \leq m \leq 10^5. This is only possible thanks to the restricted number of modulo values allowed by the problem statement. The solution I am suggesting might break as soon you allow mm to be an order magnitude higher.

Assuming that all Pisano periods are 32-bit integers, we need only 100000 of them or 400KB of memory well within memory constraints. The scanning for all the values Pisano period took around 72 hours running on several ranges for mm concurrently in an Intel I7 CPU.

I just give you all the necessary components to create a code with the values of the Pisano periods hard coded. Stress testing my C++ version of this code, I found that it run at most ~1ms well within problem requirements!

References

  1. Algorithmic toolbox

  2. It seems I am not the only one, here a link to a blogger also impress by the problem

  3. Wiki entry about Pisano period, Wolfram entry about Pisano 

  4. In principle complexity goes from O(n2)O(n^2) to O(n mod π(m))O(n \text{ mod } \pi(m)) in case the value of mm can be fixed within 32 bit integer. This is because each iteration sum when computing FibonacciModulo algorithm (see later in the post) will be O(1)O(1) due to the fact no intermediate number will be larger than mm

  5. Khan Academy: Modular addition and subtraction. 

  6. Like the fantastic work from Marc Renault.