尾递归意味着你必须直接返回递归调用的结果,没有进一步的操作。
映射中明显的递归是计算列表中一个元素上的函数,然后使用递归调用来处理列表的其余部分。但是,您需要将处理一个元素的结果与处理列表的其余部分的结果相结合,这需要递归调用之后的操作。
避免这种情况的一个很常见的模式是将组合内部的递归调用;你将处理后的元素作为参数传入,并使其成为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)
从而可以更简洁,Pythonically表现为其他的答案显示,而不诉诸单独的辅助功能。但是这显示了将非尾递归函数转换为尾递归函数的一般方法。
在上面,a
被称为累加器。通常的想法是将通常做的操作移到递归调用后进入下一个递归调用,通过包装工作外部调用完成“迄今”并将其传递到累加器中。
如果map
可以被看作意义“的l
每个元素上调用f
,并返回结果列表”,map_acc
可以被认为是意为“叫f
的l
每一个元素,返回列表结果结合a
,已经产生的结果列表“。
来源
2011-08-08 01:43:32
Ben
为什么这个函数不是尾递归? –
它不是因为映射不是最后一个操作,映射的结果被添加到[f(l [0])],所以递归过程中的每个堆栈帧将需要 – Joe
好吧,所以你递归地生成元素,然后合并他们到一个列表。迭代过程会在每一步中更新列表的状态,并将其传递给下一次迭代,对吧?你需要更多的帮助吗? –