rgrinberg 提供的解决方案可以泛化,以便我们可以记忆任何函数。我将使用关联列表而不是哈希表。但这并不重要,您可以轻松地将我的所有示例转换为使用哈希表。
首先,这里有一个函数memo
,它接受另一个函数并返回它的记忆版本。这是 nlucaroni 在其中一条评论中建议的:
let memo f =
let m = ref [] in
fun x ->
try
List.assoc x !m
with
Not_found ->
let y = f x in
m := (x, y) :: !m ;
y
该函数memo f
保留m
到目前为止计算的结果列表。当被要求计算f x
时,首先检查m
是否f x
已经计算过。如果是,则返回结果,否则实际计算f x
,将结果存储在 中m
,然后返回。
在递归的情况下,上述memo
情况存在问题。f
一旦memo
调用f
了 compute f x
,任何由 进行的递归调用f
都不会被memo
. 为了解决这个问题,我们需要做两件事:
在这种递归的定义中,f
我们需要将递归调用替换为对“稍后提供”的函数的调用(这将是 的记忆化版本f
)。
我们memo f
需要提供f
承诺的“当你想要进行递归调用时应该调用的函数”。
这导致以下解决方案:
let memo_rec f =
let m = ref [] in
let rec g x =
try
List.assoc x !m
with
Not_found ->
let y = f g x in
m := (x, y) :: !m ;
y
in
g
为了演示它是如何工作的,让我们记住朴素的斐波那契函数。我们需要编写它以便它接受一个额外的参数,我将其称为self
. 这个参数是函数应该使用的,而不是递归调用自身:
let fib self = function
0 -> 1
| 1 -> 1
| n -> self (n - 1) + self (n - 2)
现在为了得到记忆fib
,我们计算
let fib_memoized = memo_rec fib
欢迎您尝试一下,看看它会fib_memoized 50
立即返回。(对于通常的幼稚递归定义memo f
,情况并非如此。)f