这个memoized fibonacci函数如何工作?

时间:2013-03-21 09:53:20

标签: haskell fibonacci memoization

在我正在进行的功能编程课程的当前练习作业中,我们必须制作一个给定函数的memoized版本。为了解释memoization,给出了以下示例:

fiblist = [ fibm x | x <- [0..]]

fibm 0 = 0
fibm 1 = 1
fibm n = fiblist !! (n-1) + fiblist !! (n-2)

但我不完全明白这是如何运作的。

我们打电话给fibm 3

fibm 3
--> fiblist !! 2 + fibList 1
--> [fibm 0, fibm 1, fibm 2] !! 2 + [fibm 0, fibm 1] !! 1
--> fibm 2 + fibm 1
--> (fiblist !! 1 + fiblist 0) + 1
--> ([fibm 0, fibm 1] !! 1 + [fibm 0] !! 0) + 1
--> (fibm 1 + fibm 0) + 1
--> 1 + 0 + 1
--> 2

从其他问题/答案和谷歌搜索我了解到,不知何故,评估的fiblist在调用之间共享。

这是否意味着,例如,对于fiblist !! 2 + fiblist !! 1,列表值仅针对fiblist !! 2计算一次,然后仅用于fiblist !! 1

然后每次调用只计算两次斐波纳契数,因此没有指数的调用。但是fiblist函数中调用的“较低”级别呢?他们如何从原始fiblist电话中的计算fibm中受益?

2 个答案:

答案 0 :(得分:8)

这里的关键部分是列表被懒惰地评估,这意味着直到第一次请求时才计算该元素。然而,一旦进行了评估,就可以查找其他任何内容了。因此,在您的示例中,您说的是fiblist !! 2只计算一次值,然后重新用于fiblist !! 1

fiblist功能的“较低级别”以相同的方式工作。第一次调用fiblist !! 1时,将通过调用fibm 1(仅为1)来评估它,然后此值将保留在列表中。当您尝试提高斐波那契数字时,fiblist会调用fibm,这会在fiblist的较低位置(可能已经评估过的位置)查找这些值。

答案 1 :(得分:5)

让我们一步一步地完成评估。除了显示当前表达式之外,我们还在内存中显示fiblist的当前评估状态。在那里,我写<expr>来表示未评估的表达式(通常称为thunk),>expr<表示当前正在评估的未评估表达式。你可以看到懒惰的评估。只有在需要时才会对列表进行评估,并且将共享完成的子计算以供将来重用。

   Current expression                       Current evaluation state of fiblist

   fibm 3                                   <[ fibm x | x <- [0..] ]>

->   (simple expansion of the definition)

   fiblist !! (3-1) + fiblist !! (3-2)      <[ fibm x | x <- [0..] ]>

->   ((+) has to evaluate both its arguments to make progress, let's assume
     it starts with the left argument; (!!) traverses the list up to the given
     element and returns the element it finds)

   fibm 2 + fiblist !! (3-2)                <fibm 0> : <fibm 1> : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (simple expansion of the definition)

   (fiblist !! (2-1) + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : <fibm 1> : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (we again start with the first argument to (+),
     computing the result of (!!) does not cause any
     further evaluation of fiblist)

   (fibm 1 + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : >fibm 1< : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (expanding fibm 1 returns a result immediately;
     this concludes the computation of fibm 1,
     and the thunk is updated with the result)

   (1 + fiblist !! (2-2)) + fiblist !! (3-2)
                                            <fibm 0> : 1 : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (now we compute fiblist !! (2-2))

   (1 + fibm 0) + fiblist !! (3-2)          >fibm 0< : 1 : >fibm 2< : <[ fibm x | x <- [3..] ]>

->   (expanding fibm 0 returns 0 immediately, and the
     corresponding thunk can be updated)

   (1 + 0) + fiblist !! (3-2)               0 : 1 : >fibm 2< : <[fibm x | x <- [3..] ]>

->   (we can compute the (+), yielding the result of
     fibm 2; the corresponding thunk is updated)

   1 + fiblist !! (3-2)                     0 : 1 : 1 : <[fibm x | x <- [3..] ]>

->   (now the right argument of (+) has to be evaluated, but (!!)
     will return the already evaluated list element directly)

   1 + 1                                    0 : 1 : 1 : <[fibm x | x <- [3..] ]>

->   (arithmetic; note that as we called fibm 3 directly in the
     beginning, instead of fiblist !! 3, the list is unaffected
     by this final result)

   2                                        0 : 1 : 1 : <[fibm x | x <- [3..] ]>

由于fiblist是一个全局常量(通常称为CAF,用于&#34;常量应用形式&#34;),列表的部分评估状态将持续存在,将来对fibm的调用将会重用已经评估的列表元素。不过,该列表最终会变得越来越大,消耗的内存越来越多。