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

此代码不能正常工作(该函数的行为像一个空发生器)。

我想念什么吗?有没有一种方法可以将上述递归算法转换为生成器,而无需用迭代替换呢

Answers:


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中,您可以用替换最后两行yield from getPermutations(string[:i] + string[i+1:]),这在许多方面都更加有效!
Manuel Ebert 2014年

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
Thomas Andrews

29

这避免了len(string)-deep递归,并且通常是处理generators-inside-generators的一种好方法:

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

flatten允许我们通过简单地yield对另一个生成器进行操作,而不是对其进行迭代并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发布,这使我几秒钟就消失了!)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.