虽然您已经修复了它们,但您的原始方法存在两个问题:
函数应用程序在 SML 中是左关联的,因此 m f (n - 1) 被解释为 (m f) (n - 1),而不是所需的 m (f (n - 1))。您可以通过明确指定括号 m (f (n - 1)) 来解决此问题。
-
为了能够从m 调用f 和 从f 调用m,您需要在第一个声明中使用关键字fun 而不是val(使函数递归),并在第二个声明中使用关键字and 而不是fun 或val(使函数与第一个函数相互递归)。这看起来像
fun f n = ... (* I can call f or m from here! *)
and m n = ... (* I can call f or m from here! *)
为了让它更快,你可以记忆!诀窍是让f 和m 将它们自己的记忆版本作为参数。
(* Convenience function: Update arr[i] to x, and return x. *)
fun updateAndReturn arr i x = (Array.update (arr, i, SOME x); x)
(*
* Look up result of f i in table; if it's not found, calculate f i and
* store in the table. The token is used so that deeper recursive calls
* to f can also try to store in the table.
*)
fun memo table f token i =
case Array.sub (table, i)
of NONE => updateAndReturn table i (f token i)
| SOME x => x
(*
* Given f, g, and n : int, returns a tuple (f', g') where f' and g' are memoized
* versions of f and g, respectively. f' and g' are defined only on the domain
* [0, n).
*)
fun memoizeMutual (f, g) n =
let
val fTable = Array.array (n, NONE)
val gTable = Array.array (n, NONE)
fun fMemo i = memo fTable f (fMemo, gMemo) i
and gMemo i = memo gTable g (gMemo, fMemo) i
in
(fMemo, gMemo)
end
fun female _ 0 = 1
| female (f, m) n = n - m (f (n - 1))
fun male _ 0 = 0
| male (m, f) n = n - f (m (n - 1))
fun hofstadter upTo =
let
val (male', female') = memoizeMutual (male, female) upTo
in
(List.tabulate (upTo, male'), List.tabulate (upTo, female'))
end
我将f 和m 重命名为female 和male。记忆的fMemo 和gMemo 通过memoizeMutual 穿过female 和male。有趣的是,如果我们调用 male',那么 male' 和 female' 的结果都会被记忆。
要确认它确实更快,请尝试评估 hofstadter 10000。它比您的版本所花费的永远快得多。
最后一点,唯一的递归函数是fMemo 和gMemo。我编写的所有其他函数都可以编写为匿名函数(val memoizeMutual = fn ...、val female = fn ... 等),但我选择不这样做,因为在 SML 中编写递归函数的语法要紧凑得多。
为了概括这一点,您可以将记忆的数组版本替换为哈希表之类的东西。这样我们就不必预先指定记忆的大小。