在Haskell中,如何基于第n个Fibonacci数等于第(n-2)个Fibonacci数加上第(n-1)个Fibonacci数的属性生成Fibonacci数?
我见过这个:
fibs :: [Integer]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
我真的不明白,或者它是如何产生无限列表而不是包含3个元素的列表。
我如何通过计算实际定义编写haskell代码,而不是通过列表函数做一些非常奇怪的事情?
答案 0 :(得分:80)
这是一个计算第n个Fibonacci数的另一个更简单的函数:
fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
您所指的实现是关于Fibonacci中的值如何相互关联的一些观察的继承(以及Haskell如何根据自身定义数据结构实际上创建无限数据结构)
你问题中的函数是这样的:
假设您已经拥有无限的Fibonacci数字列表:
[ 1, 1, 2, 3, 5, 8, 13, .... ]
此列表的tail
是
[ 1, 2, 3, 5, 8, 13, 21, .... ]
zipWith
使用给定的运算符逐个元素地组合两个列表:
[ 1, 1, 2, 3, 5, 8, 13, .... ]
+ [ 1, 2, 3, 5, 8, 13, 21, .... ]
= [ 2, 3, 5, 8, 13, 21, 34, .... ]
因此可以通过将元素1
和1
添加到使用Fibonacci数的无限列表的尾部压缩无限的Fibonacci数列表的结果来计算Fibonacci数的无限列表。 +
运营商。
现在,要获得第n个Fibonacci数,只需获得Fibonacci数无限列表的第n个元素:
fib n = fibs !! n
Haskell的优点在于它不需要计算Fibonacci数列表中的任何元素。
我的头爆了吗? :)
答案 1 :(得分:22)
按照定义,斐波纳契系列的每个项目都是前两个项的总和。将这个定义放入lazy haskell给你这个!
fibo a b = a:fibo b (a+b)
现在只需从0开始从fibo中取n个项目
take 10 (fibo 0 1)
答案 2 :(得分:19)
扩展dtb的答案:
“简单”解决方案之间存在重要差异:
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
你指定的那个:
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
简单的解决方案需要O(1.618NN)时间来计算第N个元素,而你指定的那个元素需要O(N 2 )。那是因为您指定的那个考虑到计算fib n
和fib (n-1)
(计算它需要)共享fib (n-2)
的依赖关系,并且可以为两者计算一次省时间。 O(N 2 )用于N个O(N)数字的加法。
答案 3 :(得分:5)
Fibonacci序列here有许多不同的Haskell算法。 “天真”的实现看起来就像你所追求的那样。
答案 4 :(得分:1)
unfoldr
可以很容易地实现产生无限斐波那契数列的懒惰方式,如下所示;
fibs :: [Integer]
fibs = unfoldr (\(f,s) -> Just (f,(s,f+s))) (0,1)
答案 5 :(得分:1)
斐波那契(n)的定义是:
fibonacci (n) = fibonacci (n-1) + fibonacci (n-2)
Haskell中的天真实现
fibonacci :: Integer -> Integer
fibonacci 0 = 1
fibonacci 1 = 1
fibonacci x = fibonacci (x-1) + fibonacci (x-2)
所有公式都可以追溯到这个定义,有些运行速度非常快,其中一些运行速度非常慢。上面的实现有O(n)= 2 ^ n
根据你的问题的精神,让我删除列表的使用,并给你一些在O(n)运行的东西。让我们不要将所有的斐波那维从0到n保持在列表中。
如果我们有三个(一个有三个成员的元组)看起来像:
(n, fibonacci[n-1], fibonacci[n])
记住初始定义,我们可以计算出上一个三联的下一个三元:
(n+1, fibonacci[n], fibonacci[n-1] + fibonacci[n])
= (n+1, fibonacci[n], fibonacci[n+1])
上一个三联赛的下一个三联赛:
(n+2, fibonacci[n+1], fibonacci[n] + fibonacci[n+1])
= (n+1, fibonacci[n+1], fibonacci[n+2])
依此类推 ......
n = 0 => (0,0,1)
n = 1 => (1,1,1) - calculated from the previous triple
n = 2 => (2,1,2) - calculated from the previous triple
n = 3 => (3,2,3) - calculated from the previous triple
n = 4 => (4,3,5) - calculated from the previous triple
n = 5 => (5,5,8) - calculated from the previous triple
让我们在Haskell中实现它并使用自解释变量名称:
nextTripleIfCurrentNIsLessThanN :: (Int, Integer, Integer) -> Int -> (Int, Integer, Integer)
nextTripleIfCurrentNIsLessThanN (currentN, x, y) n = if currentN < n
then nextTripleIfCurrentNIsLessThanN (currentN + 1, y, x + y) n
else (currentN, x, y)
thirdElementOfTriple :: (x,y,z) -> z
thirdElementOfTriple (x,y,z) = z
fibonacci :: Int -> Integer
fibonacci n = thirdElementOfTriple (nextTripleIfCurrentNIsLessThanN (0,0,1) n)
这将在O(n)中起作用[它是温和的二次曲线,大量出现。原因是添加大数字比添加小数字更昂贵。但这是关于计算模型的单独讨论。]
fibonacci 0
1
fibonacci 1
1
fibonacci 2
2
fibonacci 3
3
fibonacci 4
5
fibonacci 5
8
fibonacci 5000
6276302800488957086035253108349684055478528702736457439025824448927937256811663264475883711527806250329984690249846819800648580083040107584710332687596562185073640422286799239932615797105974710857095487342820351307477141875012176874307156016229965832589137779724973854362777629878229505500260477136108363709090010421536915488632339240756987974122598603591920306874926755600361865354330444681915154695741851960071089944015319300128574107662757054790648152751366475529121877212785489665101733755898580317984402963873738187000120737824193162011399200547424034440836239726275765901190914513013217132050988064832024783370583789324109052449717186857327239783000020791777804503930439875068662687670678802914269784817022567088069496231111407908953313902398529655056082228598715882365779469902465675715699187225655878240668599547496218159297881601061923195562143932693324644219266564617042934227893371179832389642895285401263875342640468017378925921483580111278055044254198382265567395946431803304304326865077742925818757370691726168228648841319231470626
答案 6 :(得分:0)
LOL,我喜欢Haskell模式匹配,但它在标准的Fibonacci函数中变得无用。标准清单由右侧构成。要使用模式匹配和缺点,必须从左侧构建列表。嗯,至少,一个安慰是,这真的很快。 ~O(n),它应该是。需要辅助函数来反转无限列表(你只能在Haskell中做的事情,欢乐),这个函数输出每个后续的运行列表,所以'last'也用在辅助函数管道中。
f (x:y:xs) = (x+y):(x:(y:xs))
助手
fib n = reverse . last . take n $ iterate f [1,0]
这是一个列表版本,我认为,它阐明了如何构建列表的目的。我想做一个元组版本。
编辑3/15/2018
首先,Will Ness启发了我的知识,即每次迭代生成的整个列表是不必要的,只需要使用最后两个值,结果列表的值是每个列表的第一个值或者对生成。太有趣了。 Will告诉我列表的值是列表的第一个值,我运行它并看到每个列表的每个头的值为0,1,1,2,3,5,8,13,我说WTF,我的PC上的代码会改变吗?价值观在那里,但如何!?过了一会儿,我意识到他们一直都在那里,但我只是没有看到他们。啊。 Will的函数和辅助函数的版本是:
f = (\(x:y:xs) -> (x+y):x:xs) -- notice, no y: put back only x+y & x
和他的辅助函数重写
fib n = map head . take n $iterate f [0,1]
我认为,他们现在可以合并:
fib n = take n . map head $ iterate (\(x:y:xs) -> (x+y):x:xs) [0,1]
作为一个无关紧要的东西,该函数也可以与元组一起
fib n = take n . map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)
另一种形式,列表理解形式,也可以为所有人编写:
fib n = take n [ fst t | t <- iterate (\(a,b) -> (b,a+b)) (0,1)]
这些都是迭代和健壮的。最快的是用于fib 5000的12.23秒列表的地图。对于fib 5000,在13.58秒时,元组理解度是第二快的。
答案 7 :(得分:0)
输入代码,您的定义是
fib :: Int -> Integer
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
-- i.e.
-- fib (n+2) = fib (n+1) + fib n
Int -> a ~= [a]
因为
from f = map f [0..] -- from :: (Int -> a) -> [a]
to = (!!) -- to :: [a] -> (Int -> a)
因此
fibs :: [Integer]
fibs = from fib
fibs !! 0 = 1
fibs !! 1 = 1
fibs !! (n+2) = fibs !! (n+1) + fibs !! n
-- or,
drop 2 fibs !! n = drop 1 fibs !! n + fibs !! n
= zipWith (+) (tail fibs) fibs !! n
-- i.e.
take 2 fibs = [1,1]
drop 2 fibs = zipWith (+) (tail fibs) fibs
-- hence,
fibs = take 2 fibs ++ drop 2 fibs
= 1 : 1 : zipWith (+) (tail fibs) fibs
fibs :: [Integer]
fibs = a
where
(a,b) = unzip $ (0,1) : zip b (zipWith (+) a b)
答案 8 :(得分:0)
我正在做CIS194的作业6,发现您可以用这种方式编写。 计算前n个元素仅需要O(n)个加法运算。
fibs2 :: [Integer]
fibs2 = [0, 1] ++ [fibs2 !! (n-1) + fibs2 !! (n-2) | n <- [2..]]
答案 9 :(得分:-1)
使用迭代
fibonaci = map fst (iterate f (0,1)) where f (x,y) = (y,x+y)
使用
take 10 fibonaci
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377]