简体   繁体   中英

Slow solution to project euler 25 using haskell

I am new to the Haskell language, and to learn I thought i would jump into some Project Euler. In Project Euler 25 we are tasked with doing the following:

The 12th term, F12, is the first term to contain three digits. What is the index of the first term in the Fibonacci sequence to contain 1000 digits?

And this is my solution to the problem:

fibGen :: Int -> Int
fibGen 0 = 0
fibGen 1 = 1
fibGen n = fibGen (n-1) + fibGen (n-2)

stepper n
  | length (show ( fibGen n )) >= 1000 = n
  | otherwise = stepper n + 1

Here n is just the starting point of the sequence. But this approach is incredibly slow, it had run over an hour before I decided to try another approach. Then I found another solution which is the following:

fibs = 0:1:(zipWith (+) fibs (tail fibs))
t = 10^999

problem_25 = length w
    where
      w = takeWhile (< t) fibs

And this is incredibly fast.

So my question is what is wrong in the first approach that is making it so slow.

So my question is what is wrong in the first approach that is making it so slow.

Your first approach has an infinite loop in the difinition of stepper , but nevertheless, even if there was no infinite loop it would take a considerable amount of time, due to the exponential branching strategy.


Your first approach results in exponential recursion. Indeed, except for the two base cases, all other cases will result in two extra calls:

fibGen :: Int -> Int
fibGen 0 = 0
fibGen 1 = 1
fibGen n =  + 

So this means that for fibGen 5 for example, we will evaluate this as:

fibGen 5
  - fibGen 4
    - fibGen 3
      - fibGen 2
        - fibGen 1
        - fibGen 0
      - fibGen 1
    - fibGen 2
      - fibGen 1
      - fibGen 0
  - fibGen 3
    - fibGen 2
      - fibGen 1
      - fibGen 0
    - fibGen 1

So in order to calculate fibGen 5 , we thus will make a total of 15 calls. One for fibGen 4 , two for fibGen 3 , three for fibGen 2 , five for fibGen 1 , and three for fibGen 0 .

Each time we increment n , we will almost double the amount of calls. It is clear that for a large n , the number of calls is that huge that a modern machine can still not process it.

Furthermore your stepper function is defined as an infinite loop. Indeed, a more verbose variant of your function is:


  | length (show ( fibGen n )) >= 1000 = n
  | otherwise = () + 1

So that means that if you calculate stepper n , and the constraint fails, you call stepper n again, and you will later add 1 to that result, but you thus get stuck in an infinite loop.

You can fix this by adding parenthesis:


  | length (show ( fibGen n )) >= 1000 = n
  | otherwise = stepper n + 1

Now eventually the program will terminate, but it will take a lot of time, due to the branching in the recursive definition. Note that each time you call fibGen , it will again branch, so that means that even if we fix the infinite loop, if we have called fibGen 5 , then if we later call fibGen 6 , we will do all the work again to calculate fibGen 5 to calculate fibGen 6 . We thus do not use memoization here.


Your second fibonacci program on the other hand generates a list, and it reuses the results in the list. fib will thus evaluate as:

   0 : 1 : zipWith …
-> 0 : 1 : 1 : zipWith …
-> 0 : 1 : 1 : 2 : zipWith …
-> 0 : 1 : 1 : 2 : 3 : zipWith …
-> 0 : 1 : 1 : 2 : 3 : 5 : zipWith …

So this will not suffer from branching, since it reuses results that are already in the list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM