Python 3.3 Как преобразовать эту рекурсивную функцию в чистую версию цикла yield?

0

Мой вопрос исходит из варианта Ханоя, который имеет четыре башни.

Я знаю эту статью, в которой говорится, что в c++ вы можете преобразовать любую рекурсивную функцию в цикл, но я знаком с Python. Я попытался прочесть десять правил, но каковы ключевые слова struct и stack для python?

Таким образом, любая статья или обсуждение для python, которая аналогична приведенной выше c++, также очень оценена. Благодарю.


Необработанная рекурсивная функция fmove (содержит другую рекурсивную функцию tmove), получает целое число, возвращает кортеж пар. Он элегантен, но бесполезен (попробуйте tmove(100) и будьте осторожны с вашей памятью).

Я хочу преобразовать его в версию с чистым контуром, так что даже 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,)

С помощью user2357112 в этом вопросе я знаю, как конвертировать рекурсивные функции, такие как tmove - return tmove (...) + CONS или другой вызов +recur (...), но когда ситуации становятся более сложными, как fmove, Я не знаю, как создать структуру, - i имеет отношение к n который отличается в другом стеке, и вы, наконец, должны использовать min чтобы получить минимальный размер кортежа как правильный вывод для текущего стека.

Это моя попытка (best(n) алгоритм ядра 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!")
  • 0
    Если вы не конвертируете что-то, использующее хвостовую рекурсию, то в итоге вы просто симулируете рекурсию со стеком и циклом. Вам редко нужно делать это, так что это всего лишь упражнение, чтобы увидеть, сможете ли вы это сделать? Если это так, я бы сделал это на языке, который вы знаете, во-первых, или перенес рекурсивную версию на C ++.
  • 1
    ИМО, что вам нужно, это подход динамического программирования, в котором вы строите матрицу DP снизу вверх. Это может или не может значительно уменьшить использование памяти в зависимости от того, можете ли вы удалить некоторые записи матрицы DP, которые больше не нужны. Вы можете Google для рекурсивного против подхода DP к решению проблем. Что касается вашего вопроса о структуре C ++, он похож на класс Python (я думаю, я не очень разбираюсь в Python)
Показать ещё 2 комментария
Теги:
algorithm
recursion

2 ответа

1

fmove выполняет min над всеми значениями своих рекурсивных вызовов и вызовом tmove поэтому в этом случае нет потоковой передачи результатов. Вам нужно 100% вызовов завершить, чтобы получить результат min.

Что касается подхода к стеку, он создает минимальный интерпретатор с двумя опкодами 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
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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню