【问题标题】:Coverting a recursive function to a tail recursive one in python在python中将递归函数转换为尾递归函数
【发布时间】:2015-04-15 11:47:21
【问题描述】:

作为练习,我在 python 中使用递归实现了 map 函数,如下所示:

#map function that applies the function f on every element of list l and returns the new list 
def map(l,f):
    if l == []:
        return []
    else:
        return [f(l[0])] + map(l[1:],f)

我知道python不支持尾递归优化,但是我将如何以尾递归的方式编写相同的函数呢?

请帮忙 谢谢你

【问题讨论】:

  • 为什么这个函数不是尾递归的?
  • 不是因为map不是最后一个操作,map的结果是加到[f(l[0])]中的,所以递归过程中的每一个栈帧都需要
  • 好的,所以您递归地生成元素,然后将它们组合到一个列表中。迭代过程会在每一步中更新列表的状态并将其传递给下一次迭代,对吗?您需要更多帮助吗?
  • 奇怪的是,我昨天已经发布了一个关于这个的答案! stackoverflow.com/questions/6964392/…

标签: python recursion tail-recursion


【解决方案1】:

尾递归意味着您必须直接返回递归调用的结果,无需进一步操作。

map 中明显的递归是在列表的一个元素上计算函数,然后使用递归调用来处理列表的其余部分。但是,您需要将处理一个元素的结果与处理列表其余部分的结果结合起来,这需要在递归调用之后进行操作。

避免这种情况的一个非常常见的模式是将组合移动内部递归调用;您将处理后的元素作为参数传入,并使其成为map 的责任的一部分,也可以进行合并。

def map(l, f):
    if l == []:
        return []
    else:
        return map(l[1:], f, f(l[0]))

现在是尾递归了!但这显然也是错误的。在尾递归调用中,我们传递了 3 个参数,但 map 只需要两个参数。然后是我们如何处理第三个值的问题。在基本情况下(当列表为空时),很明显:返回一个包含传入信息的列表。在递归情况下,我们正在计算一个新值,并且我们从顶部传入了这个额外的参数,并且我们有递归调用。新的值和多余的参数需要一起卷起来传入递归调用,这样递归调用才能尾递归。所有这些都表明以下几点:

def map(l, f):
    return map_acc(l, f, [])      

def map_acc(l, f, a):
    if l == []:
        return a
    else:
        b = a + [f(l[0])]
        return map_acc(l[1:], f, b)

正如其他答案所示,这可以更简洁和 Python 化地表达,而无需求助于单独的辅助函数。但这显示了将非尾递归函数转换为尾递归函数的一般方法。

在上面,a 被称为累加器。一般的想法是将您通常在递归调用之后执行的操作移到下一个递归调用中,方法是将外部调用“到目前为止”完成的工作包装起来,并将其传递到累加器中。

如果map 可以被认为是“在l 的每个元素上调用f,并返回结果列表”,那么map_acc 可以被认为是“调用f on l 的每个元素,返回一个结果列表,结合a,一个已经产生的结果列表”。

【讨论】:

  • 是否可以,而不是键入另一个函数,将具有默认值的第三个参数作为主函数中的参数?一旦第一次调用完成,它就会被修改。
  • @JuanGallostra 是的,这行得通(只要你小心不要改变或返回可能绑定到某些代码路径中的默认参数的值)。
  • map_acc 中的尾调用应该是 map_acc(l[1:], f, b),因为 map 只需要 2 个参数。
【解决方案2】:

这将是一个在尾递归中实现内置函数映射的示例:

def map(func, ls, res=None):
    if res is None:
        res = []
    if not ls:
        return res
    res.append(func(ls[0]))
    return map(func, ls[1:], res)

但这并不能解决python不支持TRE的问题,也就是说每个函数调用的调用栈都会一直持有。

【讨论】:

    【解决方案3】:

    这似乎是尾递归:

    def map(l,f,x=[]):
        if l == []:
            return x
        else:
            return map(l[1:],f,x+[f(l[0])])
    

    或者更紧凑的形式:

    def map(l,f,x=[]):
        return l and map(l[1:],f,x+[f(l[0])]) or x
    

    【讨论】:

    • 使用可变的默认参数不是明智之举:stackoverflow.com/questions/1132941/…
    • @mouand,我知道这个功能。正确使用函数不会出错。
    • 除非你打电话给my_list = map(empty_list, some_func); my_list.append(5)。现在每个空列表都将映射到 [5]。
    【解决方案4】:

    抱歉,这不是一个真正的答案,但实现 map 的另一种方法是用折叠的形式编写它。如果你尝试,你会发现它只会在 foldr 中“正确”出现;使用 foldl 会给你一个反向列表。不幸的是, foldr 不是尾递归,而 foldl 是。这表明关于 rev_map (返回反向列表的地图)有一些更“自然”的东西。不幸的是,我没有受过足够的教育,无法更进一步(我怀疑你可能可以概括地说没有不使用累加器的解决方案,但我个人不知道如何提出论点) .

    【讨论】:

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