web-dev-qa-db-ja.com

yieldを使用した再帰

再帰とyieldステートメントを混在させる方法はありますか?たとえば、無限数ジェネレーター(再帰を使用)は次のようになります。

def infinity(start):
    yield start
    # recursion here ...

>>> it = infinity(1)
>>> next(it)
1
>>> next(it)
2

私は試した:

def infinity(start):
    yield start
    infinity(start + 1)

そして

def infinity(start):
    yield start
    yield infinity(start + 1)

しかし、それらのどれも私が望むことをしませんでした。最初のものはstartを生成した後に停止し、2番目のものはstartを生成し、その後ジェネレーターを停止しました。

注: while-loopを使用してこれを行うことができることを知っています:

def infinity(start):
    while True:
        yield start
        start += 1

これが再帰的に実行できるかどうかを知りたいだけです。

57
juliomalegria

はい、これを行うことができます:

def infinity(start):
    yield start
    for x in infinity(start + 1):
        yield x

ただし、最大再帰深度に達するとエラーになります。

Python 3.3から、使用できるようになります

def infinity(start):
    yield start
    yield from infinity(start + 1)

ループすることなく、ジェネレータ関数を再帰的に呼び出す場合、またはyield from- ing、あなたがすることは、実際に関数本体を実行したり、何も生成したりすることなく、新しいジェネレータを構築することです。

詳細については、 PEP 38 を参照してください。

119
Sven Marnach

場合によっては、ジェネレーターに対して再帰ではなくスタックを使用することが望ましい場合があります。スタックとwhileループを使用して再帰的なメソッドを書き換えることができるはずです。

コールバックを使用し、スタックロジックを使用して書き換え可能な再帰メソッドの例を次に示します。

def traverse_tree(callback):
    # Get the root node from somewhere.
    root = get_root_node()
    def recurse(node):
        callback(node)
        for child in node.get('children', []):
            recurse(child)
    recurse(root)

上記のメソッドは、各ノードが子ノードを含む可能性のあるchildren配列を持つノードツリーを走査します。各ノードが検出されると、コールバックが発行され、現在のノードが渡されます。

この方法をこのように使用して、各ノードのプロパティを出力することができます。

def callback(node):
    print(node['id'])
traverse_tree(callback)

代わりにスタックを使用し、ジェネレータとしてトラバーサルメソッドを記述します

# A stack-based alternative to the traverse_tree method above.
def iternodes():
    stack = [get_root_node()]
    while stack:
        node = stack.pop()
        yield node
        for child in reversed(node.get('children', [])):
            stack.append(child)

(元と同じ走査順序が必要な場合は、スタックに追加された最初の子が最後にポップされるため、子の順序を逆にする必要があることに注意してください。)

これで、上記のtraverse_treeと同じ動作を得ることができますが、ジェネレーターを使用します:

for node in iternodes():
    print(node['id'])

これは万能のソリューションではありませんが、一部のジェネレーターでは、スタック処理を再帰に置き換えてニースの結果が得られる場合があります。

11
t.888
def lprint(a):
    if isinstance(a, list):
        for i in a:
            yield from lprint(i)
    else:
        yield a

a = [[1, [2, 3], 4], [5, 6, [7, 8, [9]]]]
for i in lprint(b):
    print(i)