我坚持使用Project Euler的Problem 2,它利用了fibonnacci序列。
我的第一个天真实现基于纯粹的数学定义,使用递归函数:
fibonnacci_coefficient :: (Eq a, NUm a) => a -> a
fibonnacci_coefficient n
| n == 0 = 1
| n == 1 = 2
| otherwise = fibonnacci_coefficient (n-1) + fibonnacci_coefficient (n-2)
练习要求总和系数,即不超过4,000,000的系数。当我启动算法时,花了超过45分钟,所以我取消了它。我想这是因为我使用这个函数依赖于递归地重新计算前一个元素的每个步骤。
在我的阅读中,我碰巧看到了一个高效的' fibonnacci序列的定义:
fib = 1 : 2 : [ a+b | (a,b) <- zip fib (tail fib)]
老实说,我觉得,我明白为什么会有效。我明白了,我有点理解,但是我不能直观地从数学定义直接切换到这样的Haskell定义。
据我了解,效率依赖于主要概念来定义追逐其尾部的列表。但我无法掌握这种模式。要理解我认为我需要看到整个模式:
给定一个数学序列,这取决于k个索引,让我们说k = 4,给定函数f,它带有4个参数:
u(n) = f (u(n-1)) (u(n-2)) (u(n-3)) (u(n-4))
将此列表表示为无限列表/序列的Haskell模式是什么?
答案 0 :(得分:3)
假设序列的前四个元素是a,b,c,d
,那么u(n) = f(u(n-1), u(n-2), u(n-3), u(n-4))
可以在Haskell中转换为递归定义的列表:
u = a : b : c : d : zipWith4 f (tail (tail (tail u))) (tail (tail u)) (tail u) u
答案 1 :(得分:3)
有人提到,编写一个同时适用于所有k
的功能并不容易。如果您希望它在参数数量中是类型安全的,那么这是正确的。但是,如果您愿意让函数f
取代 list 参数,那么就可以(从@bheklilr窃取函数名称):
import Data.List (tails)
recSeqK :: Int -> ([a] -> a) -> [a] -> [a]
recSeqK k f firstN = l where
l = firstN ++ map (f . reverse . take k) (tails l)
fib = recSeqK 2 sum [1,2]
main = print $ take 50 fib
请注意,只需要reverse
,因为使用问题中f
的{{1}}的相反参数顺序编写它更容易。
答案 2 :(得分:2)
Prelude中有一个名为iterate
的函数,它允许您根据重复的应用程序定义无限序列:
-- example: natural numbers
nats :: [Integer]
nats = iterate (+1) 0
iterate
实现为
iterate :: (a -> a) -> a -> [a]
iterate f x = x : iterate f (f x)
这是如何工作的?好吧,我们使用当前值作为结果列表的新元素,并再次使用f x
作为新的当前值调用iterate。我们可以使用此模式来定义具有更多参数的其他iterate
变体:
iterate2 :: (a -> a -> a) -> a -> a -> [a]
iterate2 f x y = x : iterate2 f y (f x y)
iterate3 :: (a -> a -> a -> a) -> a -> a -> a -> [a]
iterate3 f x y z = x : iterate3 f y z (f x y z)
iterate4 :: (a -> a -> a -> a -> a) -> a -> a -> a -> a -> [a]
iterate4 f a b c d = a : iterate4 f b c d (f a b c d)
-- and so on
-- example:
fibs :: [Integer]
fibs = iterate2 (+) 0 1
-- note: arguments of f are from last to first, so
-- we need to reverse the order of arguments
u = iterate4 (\a b c d -> f d c b a) x y v w
但是,正如您所看到的,步进函数具有另一种类型,具体取决于我们要使用的先前值的数量。这也是我们zipWith
,zipWith3
,zipWith4
等原因。虽然可以创建一个类型类以便为固定个实例创建单个函数,但是不可能为任意序列执行此操作。
话虽如此,你可能对其中一个简单的功能感到满意。