数组有 O(1) 索引。问题是每个元素都是惰性计算的。所以当你在 ghci 中运行它时会发生这种情况:
*Main> :set +s
*Main> let t = 100000
(0.00 secs, 556576 bytes)
*Main> let a = fibArray t
Loading package array-0.4.0.0 ... linking ... done.
(0.01 secs, 1033640 bytes)
*Main> a!t -- result omitted
(1.51 secs, 570473504 bytes)
*Main> a!t -- result omitted
(0.17 secs, 17954296 bytes)
*Main>
请注意,在已经查找过一次之后,查找速度非常快。该array
函数创建一个指向 thunk 的指针数组,这些指针最终将被计算以产生一个值。第一次评估价值时,您需要支付此成本。以下是用于评估的 thunk 的前几个扩展a!t
:
a!t -> a!(t-1)+a!(t-2)-> a!(t-2)+a!(t-3)+a!(t-2) -> a!(t-3)+a!(t-4)+a!(t-3)+a!(t-2)
昂贵的不是计算本身的成本,而是需要创建和遍历这个非常大的 thunk。
我尝试对传递给的列表中的值进行严格限制array
,但这似乎导致了无限循环。
解决此问题的一种常见方法是使用可变数组,例如 STArray。元素可以在数组创建期间可用时更新,最终结果被冻结并返回。在 vector 包中,create
和constructN
函数提供了简单的方法来做到这一点。
-- constructN :: Unbox a => Int -> (Vector a -> a) -> Vector a
import qualified Data.Vector.Unboxed as V
import Data.Int
fibVec :: Int -> V.Vector Int64
fibVec n = V.constructN (n+1) c
where
c v | V.length v == 0 = 0
c v | V.length v == 1 = 1
c v | V.length v == 2 = 1
c v = let len = V.length v
in v V.! (len-1) + v V.! (len-2)
但是,该fibVec
函数仅适用于未装箱的向量。正则向量(和数组)不够严格,导致回到您已经发现的相同问题。不幸的是,没有 Unboxed 实例Integer
,因此如果您需要无界整数类型(这fibVec
在此测试中已经溢出),您将不得不在其中创建一个可变数组IO
或ST
启用必要的严格性。