生成器可以递归吗?

时间:2016-07-07 20:01:20

标签: python recursion generator

我天真地试图创建一个递归生成器。没工作。这就是我所做的:

def recursive_generator(lis):
    yield lis[0]
    recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(k)

我得到的只是第一项6

有没有办法让这些代码有效?基本上将yield命令转移到递归方案中的上一级?

6 个答案:

答案 0 :(得分:69)

试试这个:

def recursive_generator(lis):
    yield lis[0]
    yield from recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(k)

我应该指出,由于你的功能存在错误,这不起作用。它应该包括lis不为空的检查,如下所示:

def recursive_generator(lis):
    if lis:
        yield lis[0]
        yield from recursive_generator(lis[1:])

如果您使用的是Python 2.7并且没有yield fromcheck this question out.

答案 1 :(得分:18)

为什么您的代码没有完成工作

在您的代码中,生成器函数:

  1. 返回(产生)列表的第一个值
  2. 然后它创建一个新的迭代器对象调用相同的生成器函数,将列表的一部分传递给它
  3. 然后停止
  4. 迭代器的第二个实例,一个递归创建,永远不会被迭代。这就是为什么你只得到列表的第一项。

    生成器函数对于自动创建迭代器对象(实现iterator protocol的对象)非常有用,但是您需要迭代它:手动调用{{1对象的方法或通过循环语句自动使用迭代器协议。

    那么,我们可以递归调用生成器吗?

    答案是。现在回到你的代码,如果真的想用生成器函数做这个,我想你可以试试:

    next()

    注意:项目以相反的顺序返回,因此您可能希望在第一次调用生成器之前使用def recursive_generator(some_list): """ Return some_list items, one at a time, recursively iterating over a slice of it... """ if len(some_list)>1: # some_list has more than one item, so iterate over it for i in recursive_generator(some_list[1:]): # recursively call this generator function to iterate over a slice of some_list. # return one item from the list. yield i else: # the iterator returned StopIteration, so the for loop is done. # to finish, return the only value not included in the slice we just iterated on. yield some_list[0] else: # some_list has only one item, no need to iterate on it. # just return the item. yield some_list[0] some_list = [6,3,9,1] for k in recursive_generator(some_list): print(k)

    在这个例子中需要注意的重要事项是:生成器函数在 for 循环中递归调用自身,它会看到一个迭代器并自动在其上使用迭代协议,所以它实际上从它获得了价值。

    这有效,但我认为这实在没用。我们正在使用生成器函数迭代列表并且只是一次一个地获取项目,但是...列表本身是可迭代的,因此不需要生成器! 当然我明白了,这只是一个例子,也许这个想法有用。

    另一个例子

    让我们回收上一个例子(对于懒惰)。让我们说我们需要在列表中打印项目,向每个项目添加先前项目的计数(只是一个随机的例子,不一定有用)。

    代码如下:

    some_list.reverse()

    现在,正如您所看到的,生成器函数在返回列表项之前实际上正在执行某些操作,并且递归的使用开始有意义。不过,这只是一个愚蠢的例子,但你明白了。

    注意:当然,在这个愚蠢的例子中,列表应该只包含数字。如果你真的想尝试打破它,只需在 some_list 中输入一个字符串并享受乐趣。同样,这只是一个示例,而不是生产代码!

答案 2 :(得分:7)

递归生成器对于遍历非线性结构非常有用。例如,让二叉树为None或值的元组,左树,右树。递归生成器是访问所有节点的最简单方法。例如:

tree = (0, (1, None, (2, (3, None, None), (4, (5, None, None), None))),
        (6, None, (7, (8, (9, None, None), None), None)))

def visit(tree):  # 
    if tree is not None:
        try:
            value, left, right = tree
        except ValueError:  # wrong number to unpack
            print("Bad tree:", tree)
        else:  # The following is one of 3 possible orders.
            yield from visit(left)
            yield value  # Put this first or last for different orders.
            yield from visit(right)

print(list(visit(tree)))

# prints nodes in the correct order for 'yield value' in the middle.
# [1, 3, 2, 5, 4, 0, 6, 9, 8, 7]

修改:将if tree替换为if tree is not None,以将其他错误值视为错误。

答案 3 :(得分:1)

直到Python 3.4,生成器函数在完成时必须引发StopIteration异常。 对于递归情况,其他异常(例如IndexError)早于StopIteration引发,因此我们手动添加它。

def recursive_generator(lis):
    if not lis: raise StopIteration
    yield lis[0]
    yield from recursive_generator(lis[1:])

for k in recursive_generator([6, 3, 9, 1]):
    print(k)
def recursive_generator(lis):
    if not lis: raise StopIteration
    yield lis.pop(0)
    yield from recursive_generator(lis)

for k in recursive_generator([6, 3, 9, 1]):
    print(k)

请注意,for循环将捕获StopIteration异常。 有关此here

的更多信息

答案 4 :(得分:0)

是的,您可以拥有递归生成器。但是,它们具有与其他递归函数相同的递归深度限制。

def recurse(x):
  yield x
  yield from recurse(x)

for (i, x) in enumerate(recurse(5)):
  print(i, x)

此循环在崩溃前达到3000(对我来说)。

但是,您可以通过一些技巧来创建一个将生成器馈入其自身的函数。这使您可以像递归生成器那样编写生成器,但不能:https://gist.github.com/3noch/7969f416d403ba3a54a788b113c204ce

答案 5 :(得分:0)

您的递归调用仅执行一次的原因是您实际上是在创建嵌套生成器。也就是说,每次递归调用函数recursive_generator时,您都将在生成器内部创建一个新生成器。

尝试以下操作,您将看到。

def recursive_generator(lis):
    yield lis[0]
    yield recursive_generator(lis[1:])

for k in recursive_generator([6,3,9,1]):
    print(type(k))

一个简单的解决方案,就像其他人提到的那样,是使用yield from