如何记忆递归函数?

时间:2014-09-06 21:26:03

标签: recursion ocaml memoization

考虑一个递归函数,比如由以下定义的Euclid算法:

let rec gcd a b =
  let (q, r) = (a / b, a mod b) in
  if r = 0 then b else gcd b r

(这是一个简化的,非常脆弱的定义。)如何记忆这样的功能?定义高阶函数的经典方法memoize : ('a -> 'b) -> ('a -> 'b) 添加memoization到函数是没用的,因为它只会节省第一次调用的时间。

我已经找到了如何在Lisp或Haskell中记忆这样的函数的细节:

这些建议依赖于Lisp中的能力来覆盖函数的符号定义或Haskell使用的“按需调用”策略,因此在OCaml中无用。

2 个答案:

答案 0 :(得分:5)

获胜策略是在延续传递样式中定义要记忆的递归函数:

let gcd_cont k (a,b) =
  let (q, r) = (a / b, a mod b) in
  if r = 0 then b else k (b,r)

我们不是递归地定义gcd_cont函数,而是添加一个参数,即要继续调用“continuation”而不是递归。现在我们定义两个高阶函数callmemo,它们对具有continuation参数的函数进行操作。第一个函数call定义为:

let call f =
    let rec g x =
      f g x
    in
    g

它构建了一个函数g,它没有任何特殊功能,只能调用f。第二个函数memo构建了一个实现memoization的函数g

let memo f =
    let table = ref [] in
    let compute k x =
      let y = f k x in
      table := (x,y) :: !table; y
    in
    let rec g x =
      try List.assoc x !table
      with Not_found -> compute g x
    in
    g

这些功能具有以下签名。

val call : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
val memo : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>

现在我们定义gcd函数的两个版本,第一个没有memoization,第二个带memoization:

let gcd_call a b =
  call gcd_cont (a,b)

let gcd_memo a b =
  memo gcd_cont (a,b)

答案 1 :(得分:1)

# let memoize f =
    let table = Hashtbl.Poly.create () in
    (fun x ->
      match Hashtbl.find table x with
      | Some y -> y
      | None ->
        let y = f x in
        Hashtbl.add_exn table ~key:x ~data:y;
        y
    );;
val memoize : ('a -> 'b) -> 'a -> 'b = <fun>


# let memo_rec f_norec x =
    let fref = ref (fun _ -> assert false) in
    let f = memoize (fun x -> f_norec !fref x) in
    fref := f;
    f x
  ;;
val memo_rec : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>

您应该阅读 Real World OCaml 一书中的https://realworldocaml.org/v1/en/html/imperative-programming-1.html#memoization-and-dynamic-programming部分。

它将帮助您真正了解memo的工作原理。