【问题标题】:Recursive iteration of a function getting stack overflows获得堆栈溢出的函数的递归迭代
【发布时间】:2021-08-10 11:42:53
【问题描述】:

我正在尝试编写一个 Maxima 函数来迭代作为参数提供的另一个函数。目标基本上是……

iter(f,0) ........ gives the identity function lambda([x],x)
iter(f,1) ........ gives f
iter(f,2) ........ gives lambda([x],f(f(x))
iter(f,3) ........ gives lambda([x],f(f(f(x)))

原因是试图弄清楚迭代多项式的行为方式 - 类似于 Robert May 人口方程,但多项式不同。

无论如何,我对 Maxima 还是很陌生(至少对于看起来更像是简单编程而不是仅仅寻求解决方案的事情)并且经过一段时间试图找出我做错了什么之后,我想我已经消除了所有愚蠢的错误,我必须对 Maxima 的工作原理有一个更根本的误解。

我有什么...

iter(f,n) := if is (n=0)
  then lambda ([x], x)
  else block ([n2: floor (n/2),
               nr: is (n2*2#n),
               ff: iter (f,n2) ], if nr then lambda ([x],f(ff(ff(x))))
                                        else lambda ([x],  ff(ff(x)) ));

千里马接受这一点。现在作为一个简单的示例函数进行迭代...

inc(x):=x+1;

还有一些测试——首先是基本情况……

iter(inc,0);

这行得通 - 它按预期提供lambda([x],x)。接下来,“迭代”一次……

iter(inc,1);

我期待与inc 等效的内容,但由于其编写方式,更像lambda([x],inc(identity(identity(x))),但更混乱。我实际上得到的是堆栈溢出......

Maxima encountered a Lisp error:
Control stack exhausted (no more space for function call frames).
This is probably due to heavily nested or infinitely recursive function
calls, or a tail call that SBCL cannot or has not optimized away.
...

我不明白为什么is (n=0) 基本情况检查无法在递归调用中发现这一点,所以我不明白为什么n=1 的这个iter 函数会被输入两次以上 -耗尽堆栈似乎非常极端。

当然,一旦我有了基本的想法,我可能会将特殊情况 n=1 作为有效的另一个基本情况来提高效率(结果函数定义不太混乱)并添加更多检查,但我只想要一些不t 堆栈溢出现在在微不足道的情况下。

我误会了什么?

【问题讨论】:

    标签: recursion maxima


    【解决方案1】:

    这就是我想出的。有必要替换到 lambda 的主体中,因为主体没有被评估——我想你已经遇到了这个重要的点。

    (%i3) iter(f, n) := if n = 0 then identity elseif n = 1 then f
                else subst([ff = iter(f, n - 1),'f = f],
                           lambda([x], f(ff(x)))) $
    (%i4) iter(inc, 0);
    (%o4)                       identity
    (%i5) iter(inc, 1);
    (%o5)                          inc
    (%i6) iter(inc, 2);
    (%o6)               lambda([x], inc(inc(x)))
    (%i7) iter(inc, 3);
    (%o7)             lambda([x], inc(inc(inc(x))))
    (%i8) iter(inc, 4);
    (%o8)          lambda([x], inc(inc(inc(inc(x)))))
    (%i9) inc(u) := u + 1 $
    (%i10) iter(inc, 4);
    (%o10)               lambda([x], inc(x + 3))
    (%i11) %(10);
    (%o11)                         14
    (%i12) makelist (iter(cos, k), k, 0, 10);
    (%o12) [identity, cos, lambda([x], cos(cos(x))), 
    lambda([x], cos(cos(cos(x)))), lambda([x], 
    cos(cos(cos(cos(x))))), lambda([x], cos(cos(cos(cos(cos(x)))))), 
    lambda([x], cos(cos(cos(cos(cos(cos(x))))))), 
    lambda([x], cos(cos(cos(cos(cos(cos(cos(x)))))))), 
    lambda([x], cos(cos(cos(cos(cos(cos(cos(cos(x))))))))), 
    lambda([x], cos(cos(cos(cos(cos(cos(cos(cos(cos(x)))))))))), 
    lambda([x], cos(cos(cos(cos(cos(cos(cos(cos(cos(cos(x)))))))))))]
    (%i13) map (lambda([f], f(0.1)), %);
    (%o13) [0.1, 0.9950041652780257, 0.5444993958277886, 
    0.8553867058793604, 0.6559266636704799, 0.7924831019448093, 
    0.7020792679906703, 0.7635010336918854, 0.7224196362389732, 
    0.7502080588752906, 0.731547032044224]
    

    Maxima 几乎擅长这样的事情——因为它是建立在 Lisp 之上的,所以存在正确的概念元素。但是,在处理此类问题时,缺少词法范围是一个严重的问题,因为这意味着当您在函数定义中引用 f 时,它与可能存在于它之外的 f 相同。如果解决方案依赖于仔细区分您的意思是哪个f,那就有问题了。

    无论如何,我希望这个解决方案在某种程度上对您有用。

    【讨论】:

    • 好的,很有趣。你没有像我那样分而治之,但我只是出于习惯这样做——我怀疑当真正的成本隐藏在 Maximas 自己的函数/表达式操作中时,这些表达式会迅速获得性能优势大。您的方法肯定会在简单性上获胜,并且不需要那些嵌套的 block 表达式。
    • 顺便说一句 - 感谢快速列表处理 101
    【解决方案2】:

    早些时候,经过片刻的灵感,我在Maxima中尝试了以下...

    block([a:1,b:a],b);
    

    这给了我a 我期待1 的位置,这表明b:a 变量定义在同一块中看不到a:1 变量定义。我曾假设 block 中以后的变量定义能够看到早期的定义,这会影响我的 iter 函数中的两个变量定义 - 特别是,iter (f,n2) 看不到 n2 的定义,这打破了递归中的基本情况检查。

    我现在所拥有的(警告 - 不是一个有效的解决方案)是......

    iter(f,n) := if is (n=0)
      then lambda ([x], x)
      else block ([n2: floor (n/2)],
               block ([g: iter (f,n2)],
                   if is (n2*2#n) then lambda ([x],f(g(g(x))))
                                  else lambda ([x],  g(g(x)) )));
    

    我将一个块嵌套在另一个块中,以便后面的变量定义可以看到前面的。没有nr(n 是四舍五入的?)变量,尽管 TBH 保持它不需要第三个嵌套块。我在某个时候用g 替换了ff

    这解决了堆栈溢出问题 - 递归的基本情况现在似乎得到了正确处理。

    这仍然不起作用 - 似乎对 g 的引用现在由于某种原因看不到 g 的定义。

    iter(inc,0) ................. lambda([x],x)
    iter(inc,1) ................. lambda([x],f(g(g(x))))
    iter(inc,2) ................. lambda([x],g(g(x)))
    ...
    

    当需要递归半尺寸迭代g 时,由于某种原因它没有被替换。同样值得注意 - f 也没有被替换。

    作为一个最佳猜测,这可能是由于函数调用在生成的 lambda 中是按名称调用的,并且没有强制它们被替换或强制简化整个表达式。

    (更新 - This SO question 表明我已经理解了这个问题,但解决方案在我的情况下似乎不起作用 - 我试图替换的内容无论如何都是通过变量引用的。)

    但这也是一个不同的问题(这不是递归/堆栈溢出问题),所以如果我无法弄清楚,我会回来问另一个问题。如果/当我弄清楚时,我也会在这里添加一个可行的解决方案。


    我尝试了更多使用subst 和双引号表示法的方法,但Maxima 固执地一直按名称引用fg。经过一番思考,我改变了方法——而不是生成一个函数,而是生成一个表达式。工作结果是……

    iter(v,e,n) := if is (n=0)
      then ''v
      else block ([n2: floor (n/2)],
               block ([g: iter (v,e,n2)],
                   block ([gg: subst([''v=g], g)],
                       if is (n2*2#n) then subst([''v=e], gg)
                                      else gg                )));
    

    三个嵌套的block 表达式很烦人——我可能仍然缺少一些对任何有任何Maxima 经验的人来说都是显而易见的东西。此外,这很脆弱 - 它可能需要一些参数检查,但不是在每个递归调用中。最后,它不会简化结果 - 它只是通过对其自身应用直接替换来构建一个表达式。

    【讨论】:

      【解决方案3】:

      如果你用表达式来做所有事情呢?

      (%i1) iter(e, n):= block([ans: e], thru n - 1 do ans: subst('x = e, ans), ans) $
      
      (%i2) iter(x^2 + x, 1);
                                           2
      (%o2)                               x  + x
      (%i3) iter(x^2 + x, 2);
                                      2     2    2
      (%o3)                         (x  + x)  + x  + x
      (%i4) iter(x^2 + x, 3);
                           2     2    2     2     2     2    2
      (%o4)             ((x  + x)  + x  + x)  + (x  + x)  + x  + x
      

      你可以在最后定义一个函数:

      (%i5) define(g(x), iter(x^2 + x, 3));
      

      【讨论】:

        猜你喜欢
        • 2017-10-20
        • 1970-01-01
        • 2013-04-05
        • 1970-01-01
        • 2011-02-26
        • 1970-01-01
        相关资源
        最近更新 更多