作为练习,我使用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不支持尾递归优化这一事实,但我将如何以尾递归方式编写相同的函数?。
请帮助 谢谢
答案 0 :(得分:7)
尾递归意味着您必须直接返回递归调用的结果,而无需进一步操作。
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
被称为累加器。一般的想法是将通常在递归调用之后执行的操作移动到下一个递归调用中,通过包装工作外部调用已完成“到目前为止”并在累加器中传递它。 / p>
如果可以将map
视为“f
的每个元素上的”l
,并返回结果列表“,则map_acc
可以被认为是表示在f
的每个元素上调用l
,返回与a
结合的结果列表,已生成的结果列表“。
答案 1 :(得分:0)
这将是尾递归中内置函数映射的实现示例:
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支持的问题,这意味着每个函数调用的调用栈将一直保持。
答案 2 :(得分:0)
这似乎是尾递归的:
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
答案 3 :(得分:0)
不是真的答案,对不起,但实现地图的另一种方法是用折叠来写。如果你试试,你会发现它只有“折叠”出来“正确”;使用foldl会给出一个反向列表。不幸的是,foldr不是尾递归,而foldl是。这表明rev_map有更“自然”的东西(地图返回反向列表)。不幸的是,我没有受到足够的教育,无法进一步采取这一点(我怀疑你可能能够概括这一点,说没有解决方案不使用累加器,但我个人不知道如何进行论证)