Haskell: Improving my tail-recursive fibonacci implementation

好久不见. 提交于 2019-12-05 20:03:49

Your algorithm is tail-recursive, but it looks like it has other drawbacks, namely 1) you are building the result list by appending to the end of it, and 2) it's not lazy.

For 1), note that when you append two lists a ++ b, you essentially have to reallocate a. In your case a is the growing list of fibonacci numbers and b are the next two terms. So each iteration reallocates the fibonacci numbers that have already been computed and adds on two more elements which will result in quadratic running time. It would be more efficient to prepend b to the front of a. You'll be producing the fibonacci numbers in reverse, but the running time will be linear. You can then reverse the list at the end if you want the sequence in ascending order.

Note that building the list in reverse order allows you to easily get at the last two terms of the sequence by using Code-Guru's pattern-matching idea.

For 2), note that you can't get the first element of the list until the entire computation has completed. Compare with the following lazy generation of the sequence:

fibs = 0 : (go 0 1)
  where go a b = b : go b (a+b)

Even though it looks like the recursion never stops, fibs is only evaluated for as much as is needed. For example, fibs !! 3 only calls go a couple of times.

I'm not going to go to the algorithm itself, but here's some advice on how to structure your recursive functions.

First, here's how you would format your code in a separate file

fibo :: Integral x => [x]->x->x->x->[x]
fibo l x y 0 = l
fibo l x y n = fibo (l ++ [y+x] ++ [y+x+y]) (x+y) (y+x+y) (n-1)

If you save this as fibo.hs, then you can start GHCi with

ghci fibo.hs

to load the file at start. You can also load the file after starting GHCi with the command

:l fibo.hs

(assumming you start GHCi in the same directory where you saved fibo.hs)

Another nice feature is that now when you edit the file, you can reload all your changes by simply entering

:r

in the GHCi prompt.

Now, to get rid of the extra parameters, the usual pattern in Haskell is to refactor the recursive part to its own helper function and have the main function as an entry point that only takes the necessary parameters. For example,

fibo :: Integral x => x -> [x]
fibo n = fiboHelper [0,1] 0 1 n

fiboHelper :: Integral x => [x]->x->x->x->[x]
fiboHelper l x y 0 = l
fiboHelper l x y n = fiboHelper (l ++ [y+x] ++ [y+x+y]) (x+y) (y+x+y) (n-1)

Now you can call fibo simply with

> fibo 3
[0,1,1,2,3,5,8,13]

Also, a helper function like this that is not useful by itself is usually hidden inside the main function as an inner function using let or where.

fibo :: Integral x => x -> [x]
fibo n = fiboHelper [0,1] 0 1 n where
    fiboHelper l x y 0 = l
    fiboHelper l x y n = fiboHelper (l ++ [y+x] ++ [y+x+y]) (x+y) (y+x+y) (n-1)

An inner function like this is usually given a shorter name, because the context makes its purpose clear, so we could change the name to e.g. fibo'.

go is another commonly used name for a recursive helper function.

Just for the record: The "usual" definition for a list of Fibonacci numbers is:

fibo = 0 : scanl (+) 1 fibo
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!