我已经提出了以下尾递归斐波纳契生成器:
let {
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)
}
请原谅我把整个实现放在一行,因为我使用GHCi并且还没有完全学会如何把它放在一个文件中并运行(我还没到达那里)。我想知道的是这个电话:
fibo [0, 1] 0 1 5
可以改进。我不想用0和1传递初始列表,然后再次使用限制传递0和1。我相信可以改变实施。可以做些什么改变?
答案 0 :(得分:7)
你的算法是尾递归的,但看起来它有其它缺点,即1)你通过追加到它的末尾来构建结果列表,2)它不是懒惰的。
对于1),请注意,当您附加两个列表a ++ b
时,您基本上必须重新分配a
。在您的情况下,a
是斐波纳契数字的增长列表,b
是接下来的两个术语。因此,每次迭代都会重新分配已经计算过的斐波纳契数,并添加两个元素,这将导致二次运行时间。将b
添加到a
的前面会更有效率。你将反过来生成斐波纳契数,但运行时间是线性的。如果您希望序列按升序排列,则最后可以reverse
列表。
请注意,以相反的顺序构建列表可以让您使用Code-Guru的模式匹配理念轻松获取序列的最后两个术语。
对于2),请注意,在整个计算完成之前,您无法获取列表的第一个元素。与下面的延迟生成序列进行比较:
fibs = 0 : (go 0 1)
where go a b = b : go b (a+b)
即使递归似乎永远不会停止,fibs
只会根据需要进行评估。例如,fibs !! 3
只会多次调用go
。
答案 1 :(得分:4)
我不会去算法本身,但是这里有一些关于如何构造递归函数的建议。
首先,以下是如何在单独的文件中格式化代码
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)
如果将其保存为fibo.hs,则可以使用
启动GHCighci fibo.hs
在开始时加载文件。您也可以使用命令
启动GHCi后加载文件:l fibo.hs
(假设您在保存fibo.hs的同一目录中启动GHCi)
另一个不错的功能是,现在编辑文件时,只需输入
即可重新加载所有更改:r
在GHCi提示符中。
现在,为了摆脱额外的参数,Haskell中的通常模式是将递归部分重构为其自己的辅助函数,并将主函数作为仅接受必要参数的入口点。例如,
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)
现在,您只需使用
即可致电fibo
> fibo 3
[0,1,1,2,3,5,8,13]
此外,这样一个无用的辅助函数通常使用let
或where
作为内部函数隐藏在main函数中。
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)
这样的内部函数通常被赋予较短的名称,因为上下文明确了其目的,因此我们可以将名称更改为例如fibo'
。
go
是递归辅助函数的另一个常用名称。
答案 2 :(得分:3)
仅供记录:Fibonacci数字列表的“通常”定义是:
fibo = 0 : scanl (+) 1 fibo