Python 3.3 如何将此递归函数转换为纯 yield 循环版本?
Posted
技术标签:
【中文标题】Python 3.3 如何将此递归函数转换为纯 yield 循环版本?【英文标题】:Python 3.3 How to convert this recursive function to a pure yield loop version? 【发布时间】:2014-02-24 03:38:45 【问题描述】:我的问题来自河内的一个变体,它有四个塔。
我知道this article 上面写着在 c++ 中您可以将任何递归函数转换为循环,但我只熟悉 Python。我试图阅读这十条规则,但是关键字struct
和stack
对python 意味着什么?
因此,非常感谢任何与上述 C++ 类似的有关 Python 的文章或讨论。谢谢。
原始递归函数是fmove
(包含另一个递归函数tmove
),接收一个整数,返回一个对的元组。它很优雅但没用(试试tmove(100)
并注意你的记忆)。
我想将其转换为纯 yield 循环版本,因此即使 n 变得像 100 或 1000 这样大,我仍然可以知道元组的前 10 或 100 对是什么。
def memory(function):
"""
This is a decorator to help raw recursion
functions to avoid repetitive calculation.
"""
cache =
def memofunc(*nkw,**kw):
key=str(nkw)+str(kw)
if key not in cache:
cache[key] = function(*nkw,**kw)
return cache[key]
return memofunc
@memory
def tmove(n, a=0, b=1, c=2):
"int n -> a tuple of pairs"
if n==1:
return ((a,c),)
return tmove(n-1,a,c,b)+\
((a,c),)+\
tmove(n-1,b,a,c)
@memory
def fmove(n,a=0,b=1,c=2,d=3):
"int n -> a tuple of pairs"
if n==1:
return ((a,d),)
return min(
(
fmove(n-i,a,d,b,c) +
tmove(i,a,b,d) +
fmove(n-i,c,b,a,d)
for i in range(1,n)
),
key=len,)
在this question 中的user2357112 的帮助下,我知道如何转换像tmove
这样的递归函数——返回recur(...)+ CONS 或另一个调用+recur(...),但是当情况变得像fmove
这样更复杂,我不知道如何设计结构,--i
与n
相关,在不同的堆栈中是不同的,最后你必须使用min
来获取最小大小的元组作为当前堆栈的正确输出。
这是我的尝试(核心算法best(n)
仍然是递归函数):
@memory
def _best(n):
if n==1:
return 1,1
return min(
(
(i, 2*(_best(n-i)[1])+2**i-1)
for i in range(1,n)
),
key=lambda x:x[1],
)
def best(n):
return _best(n)[0]
def xtmove(n,a=0,b=1,c=2):
stack = [(True,n,a,b,c)]
while stack:
tag,n,a,b,c = stack.pop()
if n==1:
yield a,c
elif tag:
stack.append((False,n,a,b,c))
stack.append((True,n-1,a,c,b))
else:
yield a,c
stack.append((True,n-1,b,a,c))
def xfmove(n,a=0,b=1,c=2,d=3):
stack = [(True,n,a,b,c,d)]
while stack:
is_four,n,a,b,c,d = stack.pop()
if n==1 and is_four:
yield a,d
elif is_four:
# here I use a none-tail-recursion function 'best'
# to get the best i, so the core is still not explicit stack.
i = best(n)
stack.append((True,n-i,c,b,a,d))
stack.append((False,i,a,b,d,None))
stack.append((True,n-i,a,d,b,c))
else:
for t in xtmove(n,a,b,c):
yield t
这是测试代码。确保你能通过它。
if __name__=='__main__':
MAX_TEST_NUM = 20
is_passed = all((
fmove(test_num) == tuple(xfmove(test_num))
for test_num in range(1,MAX_TEST_NUM)
))
assert is_passed, "Doesn't pass the test."
print("Pass the test!")
【问题讨论】:
除非你要转换使用尾递归的东西,否则你最终只能用堆栈和循环来模拟递归。你很少需要这样做,所以这只是一个练习,看看你是否可以做到这一点?如果是这样,我会先用你知道的语言来做,或者将递归版本移植到 C++。 IMO 您需要的是一种动态编程方法,您可以在其中自下而上构建 DP 矩阵。这可能会或可能不会显着减少内存使用量,具体取决于您是否可以丢弃一些不再需要的 DP 矩阵条目。您可以在谷歌上搜索递归 vs DP 方法来解决问题。至于你关于 C++ struct 的问题,它类似于 Python 类(我想,我对 Python 不太熟悉) @David Ehrmann 如果您制作 Python 版本,那就太好了 :)。这不是练习,但是,这是来自练习。我对我目前的解决方案不满意。我对此很感兴趣。我也觉得,如果我知道一些通用技巧,可以像那篇 C++ 文章那样在 Python 中将递归函数转换为循环或产生循环,我解决复杂问题的能力就会提高。 @Abhishek Bansal 谢谢,我会看到的。 【参考方案1】:fmove
对其递归调用和对tmove
的调用的所有值执行min
,因此在这种情况下不会有结果流。您需要完成 100% 的调用才能获得 min
的结果。
关于堆栈方法,它正在创建一个具有 2 个操作码 True 和 False 的最小解释器。 :)
看看 tmove 如何在不使用没有生成器的语言中重复使用过时技术的情况下流式传输结果。
from itertools import chain
def xtmove(n, a=0, b=1, c=2):
"int n -> a tuple of pairs"
if n==1:
yield (a,c)
else:
for i in chain(xtmove(n-1,a,c,b), [(a,c)], xtmove(n-1,b,a,c)):
yield i
【讨论】:
【参考方案2】:经过几天的学习,在c++文章的帮助下,终于自己搞定了纯循环版。而且我认为@Javier 是对的——不可能屈服。
def best(n):
"""
n -> best_cut_number
four-towers Hanoi best cut number for n disks.
"""
stacks = [(0,n,[],None,)] #(stg,n,possible,choice)
cache=1:(1,0)
while stacks:
stg,n,possible,choice=stacks.pop()
if n in cache:
res = cache[n]
elif stg==0:
stacks.append((1,n,possible,n-1))
stacks.append((0,1,[],None))
else:
value = 2*res[0] + 2**choice-1
possible.append((value,choice))
if choice > 1:
stacks.append((1,n,possible,choice-1))
stacks.append((0,n-choice+1,[],None))
else:
res = min(possible,key=lambda x:x[0])
cache[n] = res
best_cut_number = res[1]
return best_cut_number
【讨论】:
以上是关于Python 3.3 如何将此递归函数转换为纯 yield 循环版本?的主要内容,如果未能解决你的问题,请参考以下文章