rgrinberg 提供的解决方案可以泛化,以便我们可以记忆任何函数。我将使用关联列表而不是哈希表。不过没关系,您可以轻松地将我所有的示例转换为使用哈希表。
首先,这是一个函数memo,它接受另一个函数并返回它的记忆版本。这是 nlucaroni 在其中一个 cmets 中所建议的:
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,并返回。
如果f 是递归的,则上述memo 存在问题。一旦memo调用f计算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 是通常的幼稚递归定义。)