Python:再帰アルゴリズムをジェネレータとして使用する


99

最近、私は重要な制約を持つ特定のシーケンスを生成する関数を書きました。問題は、自然な再帰的な解決策で発生しました。今では、比較的小さな入力の場合でもシーケンスが数千になるため、すべてのシーケンスをリストに入力するのではなく、アルゴリズムをジェネレーターとして使用したいと思います。

例を示します。再帰関数で文字列のすべての順列を計算したいとします。次の単純なアルゴリズムは、追加の引数「ストレージ」を取り、それが見つかるたびに置換を追加します。

def getPermutations(string, storage, prefix=""):
   if len(string) == 1:
      storage.append(prefix + string)   # <-----
   else:
      for i in range(len(string)):
         getPermutations(string[:i]+string[i+1:], storage, prefix+string[i])

storage = []
getPermutations("abcd", storage)
for permutation in storage: print permutation

(非効率性については気にしないでください。これは単なる例です。)

次に、関数をジェネレーターに変換します。つまり、ストレージリストに追加するのではなく、順列を生成します。

def getPermutations(string, prefix=""):
   if len(string) == 1:
      yield prefix + string             # <-----
   else:
      for i in range(len(string)):
         getPermutations(string[:i]+string[i+1:], prefix+string[i])

for permutation in getPermutations("abcd"):
   print permutation

このコードは機能しませ(関数は空のジェネレーターのように動作します)。

何か不足していますか?上記の再帰アルゴリズムを反復アルゴリズムに置き換えることなくジェネレータに変換する方法はありますか?

回答:


117
def getPermutations(string, prefix=""):
    if len(string) == 1:
        yield prefix + string
    else:
        for i in xrange(len(string)):
            for perm in getPermutations(string[:i] + string[i+1:], prefix+string[i]):
                yield perm

またはアキュムレータなし:

def getPermutations(string):
    if len(string) == 1:
        yield string
    else:
        for i in xrange(len(string)):
            for perm in getPermutations(string[:i] + string[i+1:]):
                yield string[i] + perm

29
Python 3.4では、最後の2行をyield from getPermutations(string[:i] + string[i+1:])で置き換えることができます。これは多くの点でより効率的です!
Manuel Ebert 14

1
それでも、何らかの方法で結果を構築する必要があります。を使用yield fromするには、アキュムレータ引数(prefix)を使用する必要があります。
Markus Jarderot、2014

提案:string[i],string[:i]+string[i+1:]ペアを返す別のジェネレーターを定義します。そして、それは次のようになりますfor letter,rest in first_letter_options(string): for perm in getPermuations(rest): yield letter+perm
トーマス・アンドリュース

29

これはlen(string)-deep再帰を回避し、一般にジェネレーター内のジェネレーターを処理するための優れた方法です。

from types import GeneratorType

def flatten(*stack):
    stack = list(stack)
    while stack:
        try: x = stack[0].next()
        except StopIteration:
            stack.pop(0)
            continue
        if isinstance(x, GeneratorType): stack.insert(0, x)
        else: yield x

def _getPermutations(string, prefix=""):
    if len(string) == 1: yield prefix + string
    else: yield (_getPermutations(string[:i]+string[i+1:], prefix+string[i])
            for i in range(len(string)))

def getPermutations(string): return flatten(_getPermutations(string))

for permutation in getPermutations("abcd"): print permutation

flattenyield反復してyield各項目を手動で実行するのではなく、単にそれを実行するだけで、別のジェネレーターで進行を続けることができます。


Python 3.3 yield fromでは構文が追加され、サブジェネレータへの自然な委任が可能になります。

def getPermutations(string, prefix=""):
    if len(string) == 1:
        yield prefix + string
    else:
        for i in range(len(string)):
            yield from getPermutations(string[:i]+string[i+1:], prefix+string[i])

20

getPermutationsへの内部呼び出し-これもジェネレーターです。

def getPermutations(string, prefix=""):
   if len(string) == 1:
      yield prefix + string            
   else:
      for i in range(len(string)):
         getPermutations(string[:i]+string[i+1:], prefix+string[i])  # <-----

あなたはforループでそれを繰り返す必要があります(@MizardXの投稿を参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.