在Mathematica中,为什么递归函数中的替换不会终止?

时间:2011-11-19 02:15:01

标签: recursion replace wolfram-mathematica

想象一下,我在Mathematica中定义了一个递归因子,如下所示:

Clear[fact]
fact[0] = 1
fact[n_] := n fact[n - 1]

评估事实[10]证实该功能有效并终止。

这是一个主要的例子,但它在这个问题中起到了作用。实际上,我的问题一般都与递归函数定义有关。

我期望评估以下替换以终止:

x fact[x-1] /. x -> 2

唉,它运行到递归深度限制:

$RecursionLimit::reclim: Recursion depth of 256 exceeded.

我希望看到类似的内容:

2 fact[2-1]

或只是值

2

更新:事实 的替代递归定义按预期工作:

Clear[fact]
fact[n_] := If[n < 1, 1, n fact[n - 1]]

但是这个事实(双关语意图;-)让我更加神秘:它为什么表现得那么不同?

我的问题有两个:

  1. 即使有内置的帮助并在网上寻找线索,我也无法解释为什么Mathematica坚持,显然,保持符号结果,而不是评估中间人&#39; ;结果并很好地终止。谁冒险进行了可行的解释?

  2. 我如何说服Mathematica根据我的预期执行(除了使用如果 []使用替代方案)?

  3. 我真的对这个感到困惑,我真的希望有人可以帮助我。

    / M炼

3 个答案:

答案 0 :(得分:6)

尝试u[x_] := x; Trace[x*u[x] /. x -> 2],首先评估xu[x]。在您的情况下,它首先尝试在将fact[x-1]替换为2之前评估x,并达到递归限制。

Attributes[ReplaceAll]表明它没有属性HoldFirst,所以它首先评估它的第一个参数。例如,

ReleaseHold@ReplaceAll[Hold[x*fact[x - 1]], x -> 2]

给出预期的2,因为它拥有第一个参数,替换,然后按预期释放保持。

另一种方法是

Unprotect[ReplaceAll]
SetAttributes[ReplaceAll, HoldFirst]
ReplaceAll[x*fact[x - 1], x -> 2]

但不要这样做。

不过有人会尽快给出更好的解释。

编辑:回答关于原因的补充问题

Clear[factif]
factif[n_] := If[n < 1, 1, n factif[n - 1]]

不会导致无限递归:以这种方式定义,factif[x]求值为If[x < 1, 1, x factif[x - 1]],因为x<1无法求值。因此,在尝试评估ReplaceAll的第一个参数后,它仍然保持这种形式,然后发生替换等。

答案 1 :(得分:5)

这是因为你正在评估这个:

fact[x-1]

在更换之前发生。只需fact[x-1]即可得到错误。

您可以像这样修复fact功能:

Clear[fact]
fact[0] = 1
fact[n_Integer?Positive] := n fact[n - 1]

然后x fact[x - 1] /. x -> 2返回2,这似乎是正确的。

请记住,您的函数参数模式fact[n_] 非常一般。例如,它允许评估fact[Integrate[Sin[x], x]]之类的内容,这可能不是您想要的。使用fact[n_Integer]更精确,并允许fact函数按您希望的方式运行。

如果您想更好地定义此功能,您可以执行以下操作:

Clear[fact]
fact::nicetrybuddy = "fact[``] is not defined.";
fact[0] = 1
fact[n_Integer?Positive] := n fact[n - 1]
fact[n_] := (Message[fact::nicetrybuddy, n]; $Failed)

因此fact["x"]之类的内容会失败并显示一条消息:

fact::nicetrybuddy: fact[x] is not defined.

答案 2 :(得分:1)

其他答案是正确的:fact在其参数被替换之前进行评估。关键问题是您已经为fact定义了整数输入,并且没有为非整数输入提供终端条件。如果你做了

Clear[fact]
fact[0] = 1
fact[n_Integer?Positive] := n fact[n - 1]

然后fact将被取消评估,直到它与正整数相匹配。

您可能需要将替换语句包装在Evaluate中,然后在替换其参数后触发fact的定义。

另一种方法可能是使用纯函数:

# fact[#-1]& @ 2

这不应该过早评估。