【问题标题】:How to convert a recursive function into a tail recursive one in SML?如何在 SML 中将递归函数转换为尾递归函数?
【发布时间】:2015-08-17 09:59:38
【问题描述】:

我正在尝试在 SML 中编写一个函数,该函数将返回列表元素的子序列,以及列表其余元素的序列。 所以,我的功能是

fun subseq (left,right,0) = (left,right) |
    subseq (left,h::t,len) =
       let
          val (left,right) = subseq ([],t,len-1)
       in
          (h::left,right)
       end;

fun subseqMain (mainList, length) = subseq ([],mainList,length);

我的问题是如何在不使用 @ 运算符的情况下,通过将其转换为尾递归函数来提高此函数的效率。

感谢您的宝贵时间!

【问题讨论】:

标签: recursion sml tail-recursion


【解决方案1】:

最简单的方法是使用rev。基本上,这个想法是在每次迭代中使用left。然后,您的左子序列将以相反的顺序排列,这就是 rev 出现的地方。不幸的是,您必须在子序列上进行两次传递,但实际上没有什么可以做的更快.您将获得的性能增益取决于您使用的编译器。 SML/NJ 执行 CPS 转换,因此与堆栈分配相比,非尾调用会产生少量的堆分配,因此您需要支付一些 GC 开销。你可以在整个 SML/NJ Basis 中看到他们在一些地方做这种事情,例如,partition 的定义是

fun partition pred l = let
      fun loop ([],trueList,falseList) = (rev trueList, rev falseList)
        | loop (h::t,trueList,falseList) = 
            if pred h then loop(t, h::trueList, falseList)
            else loop(t, trueList, h::falseList)
      in loop (l,[],[]) end

您的另一个选择是手动将其转换为延续传递样式 (CPS),您基本上将“其余计算”封装在一个函数中,我们称之为延续。当一个函数完成时,它只是将其结果传递给传入的延续。完整地解释 CPS 可能超出了这个答案的范围,所以我建议做一些谷歌搜索来真正掌握它。 partition 示例可能如下所示:

fun partition pred l = let
    fun loop([], k) = k([], [])
       |loop(h::t, k) = loop(t, fn (tr, fl) => if pred h then k(h::tr, fl) else k(tr, h::fl))
    in loop(l, fn res => res) end

也就是说,如果您不打算转换为尾递归,则有一种更直接的方法来实现您所拥有的:

fun subseq(l, 0) = ([], l)
   |subseq(h::t, i) = 
      let val (l, r) = subseq(t, i-1)
      in (h::l, r) end
   |subseq([], i) = raise Subscript

【讨论】:

    猜你喜欢
    • 2016-01-06
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-16
    相关资源
    最近更新 更多