谢谢,但我应该使用递归...
递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着要避免诸如突变、变量重新分配和其他副作用之类的事情。
- 如果输入
t为空,则返回空结果
- (归纳)
t 至少有一个元素。将子问题 rev(t[1:0) 的结果添加到第一个元素 [t[0]] 的单例列表中
def rev(t):
if not t:
return [] # 1. empty t
else:
rev(t[1:]) + [t[0]] # 2. at least one element
没有副作用的函数对于相同的输入总是会返回相同的结果。这使我们能够对其输出进行替换和函数调用,并轻松可视化其计算过程 -
rev([1,2,3,4,5])
== rev([2,3,4,5]) + [1]
== rev([3,4,5]) + [2] + [1]
== rev([4,5]) + [3] + [2] + [1]
== rev([5]) + [4] + [3] + [2] + [1]
== rev([]) [5] + [4] + [3] + [2] + [1]
== [] + [5] + [4] + [3] + [2] + [1]
== [5] + [4] + [3] + [2] + [1]
== [5,4] + [3] + [2] + [1]
== [5,4,3] + [2] + [1]
== [5,4,3,2] + [1]
== [5,4,3,2,1]
注意未决计算的金字塔形状。随着输入的增长,等待合并的单例列表的数量也在增长。如果t 非常大,则会导致堆栈溢出。
让我们看看一个小小的调整如何对流程产生重大影响 -
def rev(t, r = []):
if not t:
return r # 1. empty t, return r
else:
rev(t[1:], [t[0]] + r) # 2. at least one element, prepend to r
我们没有将待处理的计算留给我们返回,而是引入了具有默认值的第二个参数。请注意下面的可视化过程如何保持线性和平坦,而不是像金字塔一样增长 -
rev([1,2,3,4,5])
== rev([2,3,4,5], [1])
== rev([3,4,5], [2,1])
== rev([4,5], [3,2,1])
== rev([5], [4,3,2,1])
== rev([], [5,4,3,2,1])
== [5,4,3,2,1]
当递归调用是函数的最后调用时,称为tail recursive。由于递归是函数式语言中的自然循环机制,编译器会检测到这些尾调用并有效地防止堆栈增长,称为尾调用消除。
myinput = "abcdefghijklmnopqrstuvwxyz"
print("".join(rev(myinput)))
print("".join(rev(myinput * 100)))
zyxwvutsrqponmlkjihgfedcba
RecursionError: maximum recursion depth exceeded
那么为什么 Python 无法计算 rev(myinput * 100)?虽然 Python 支持许多功能特性,例如模块和一等函数,但它不提供尾调用消除,因此我们上面的 rev 函数仍然会溢出堆栈。结合其相对较小的堆栈大小,这一事实使 Python 成为真正学习递归的绝佳语言。了解其局限性后,您将了解编写高效算法意味着什么,甚至会了解堆栈安全的无限递归在其他语言中是如何实现的。
例如,这是一种通过使用 Python 的 inspect 模块实现无限递归的技术,该模块允许我们比较堆栈帧数据。递归函数转换为while 循环,当不再检测到尾调用时,返回最终结果。 Reza Bagheri 在this article 中提供了该技术的详细解释-
# tail_rec.py
import inspect
def tail_rec(func):
is_rec = False
t = []
def loop(*args):
nonlocal is_rec
nonlocal t
f = inspect.currentframe()
if f.f_back:
if f.f_back.f_back:
if f.f_code == f.f_back.f_back.f_code:
is_rec = True
t = args
return
while True:
try:
result = func(*args)
except TypeError:
raise Exception("function is not tail-recursive")
if is_rec:
is_rec = False
args = t
else:
return result
return loop
要启用优化,我们只需将 tail_rec 装饰器添加到我们的函数中 -
from tail_rec import tail_rec # <- import
@tail_rec # <- enable tail call elimination
def rev(t, r = []):
if not t:
return r
else:
return rev(t[1:], [t[0]] + r)
突然间,Python 递归限制消失得无影无踪 -
myinput = "abcdefghijklmnopqrstuvwxyz"
print("".join(rev(myinput * 100)))
zyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcbazyxwvutsrqponmlkjihgfedcba