Python 3.3如何将此递归函数转换为纯yield循环版本?

时间:2014-02-24 03:38:45

标签: python c++ algorithm recursion

我的问题来自河内的一个变种,它有四座塔。

我知道this article说在C ++中你可以将任何递归函数转换为循环,但我只熟悉Python。我尝试阅读十条规则,但关键字structstack对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 - return recur(...)+ CONS或其他调用+ recur(...),但是当情况变得更复杂,如fmove时,我不知道如何设计结构, - in相关,在不同的堆栈中是不同的,而你最后必须使用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!")

2 个答案:

答案 0 :(得分:1)

fmove对其递归调用的所有值和对min的调用执行tmove,因此在这种情况下不会有结果流。您需要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

答案 1 :(得分:0)

经过几天的学习,在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