在 Mathematica 中有一个可爱的技巧,它创建一个表作为评估函数调用作为重写规则的副作用。考虑经典的慢斐波那契
fib[1] = 1
fib[2] = 1
fib[n_] := fib[n-1] + fib[n-2]
前两行为输入 1 和 2 创建表条目。这与说的完全相同
fibTable = {};
fibTable[1] = 1;
fibTable[2] = 1;
在 JavaScript 中。 Mathematica 的第三行说“请安装一个重写规则,在用出现的实际参数替换模式变量 n_ 之后,用 fib[n-1] + fib[n-2] 替换任何出现的 fib[n_]。”重写器将迭代此过程,并最终在指数次数的重写后产生fib[n] 的值。这就像我们在 JavaScript 中使用
获得的递归函数调用形式
function fib(n) {
var result = fibTable[n] || ( fib(n-1) + fib(n-2) );
return result;
}
请注意,在进行递归调用之前,它首先检查表中我们显式存储的两个值。 Mathematica 求值器自动执行此检查,因为规则的呈现顺序很重要——Mathematica 首先检查更具体的规则,然后检查更一般的规则。这就是为什么 Mathematica 有两种赋值形式,= 和 :=:前者用于特定规则,其右侧可以在定义规则时计算;后者适用于在应用规则时必须评估右侧的一般规则。
现在,在 Mathematica 中,如果我们说
fib[4]
它被重写为
fib[3] + fib[2]
然后去
fib[2] + fib[1] + 1
然后去
1 + 1 + 1
最后是 3,它在下一次重写时不会改变。你可以想象,如果我们说fib[35],我们会产生巨大的表达式,填满内存,融化CPU。但诀窍是将最终的重写规则替换为以下内容:
fib[n_] := fib[n] = fib[n-1] + fib[n-2]
这表示“请将每个出现的fib[n_] 替换为一个表达式,该表达式将为fib[n] 的值安装新的特定规则并生成该值。”这个运行得更快,因为它扩展了规则库——值表! -- 在运行时。
我们可以在 JavaScript 中做同样的事情
function fib(n) {
var result = fibTable[n] || ( fib(n-1) + fib(n-2) );
fibTable[n] = result;
return result;
}
这比之前定义的 fib 运行得快很多。
这被称为“自动记忆化”[原文如此——不是“记忆化”而是“记忆化”,就像为自己创建备忘录一样]。
当然,在现实世界中,您必须管理所创建表的大小。要检查 Mathematica 中的表格,请执行
DownValues[fib]
要在 JavaScript 中检查它们,只需这样做
fibTable
在诸如 Node.JS 支持的 REPL 中。