【问题标题】:trying to understand recursive run of generators试图理解生成器的递归运行
【发布时间】:2019-06-21 18:07:12
【问题描述】:

我试图弄清楚如何将这段代码的运行绘制到递归树上,因为我不太确定它是如何运行的,即使我在调试时也是如此。 每个产量在做什么,为什么我都需要它们?

我尝试创建一棵树,将每次运行递归地连接到下一次运行,但我不知道 yield.data 后面是什么,其中头部是 'c'

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next


def get_reverse_iterator(head):
    if head.next:
        for datum in get_reverse_iterator(head.next):
            yield datum
    yield head.data


lst = Node('a', Node('b', Node('c')))
for x in get_reverse_iterator(lst):
    print(x)

结果应该是: C b 一个

【问题讨论】:

  • 第一个带有yield的循环可以由yield from get_reverse_iterator(head.next)编写。
  • 您能详细解释一下您希望它如何工作吗?我不太明白你的理解在哪里崩溃。
  • 它对我来说很好用,你有什么疑问?它是如何扩展的?

标签: python generator yield


【解决方案1】:

要了解它的工作原理,您需要了解递归的基本概念。假设我们不是在处理生成器;我们只想在给定头节点的情况下反向打印列表的所有节点。我们调用函数 print_reverse 传递节点作为参数。如果节点的 next 字段为空,我们只需打印该字段的 data 值。但是如果 next 不为空,则它指向一个必须在打印当前节点之前打印的节点。因此,我们再次递归调用 print_reverse 以首先打印该节点。当 print_reverse 返回时,我们现在可以打印当前节点。当然,当我们递归调用 print_reverse 来打印下一个节点时,它可能会发现它指向的另一个节点必须首先打印,我们将调用 print_reverse 再次递归。所以我们有:

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next


def print_reverse(head):
    if head.next:
        print_reverse(head.next)
    print(head.data)


lst = Node('a', Node('b', Node('c')))
print_reverse(lst)

在理解生成器问题之前必须理解上面的代码。我们不希望创建一个打印节点的 data 字段的函数 print_reverse,而是希望创建一个 yields 值的生成器函数。因此,重命名函数并将 print 函数替换为 yield 语句并将递归调用替换为 yield from 语句是有意义的:

class Node:
    def __init__(self, data, next=None):
        self.data = data
        self.next = next


def get_reverse_iterator(head):
    if head.next:
        #print_reverse(head.next)
        yield from get_reverse_iterator(head.next)
    #print(head.data)
    yield head.data

lst = Node('a', Node('b', Node('c')))

现在我们可以像这样使用生成器了:

for x in get_reverse_iterator(lst):
    print(x)

或:

l = [x in get_reverse_iterator(lst)]

但使用递归避免创建多个生成器对象的替代方法是:

def get_reverse_iterator(head):
    stack = []
    while head.next:
        stack.append(head)
        head = head.next
    yield head.data
    while len(stack):
        head = stack.pop()
        yield head.data

【讨论】:

  • 这是一个很好的比较,谢谢。一个小问题 - 为什么我需要使用生成器进行两次产量,而当我们不使用生成器时,所需要的只是打印数据?
  • 有一个生成器函数,get_reverse_iterator。当我们刚刚打印时,我们有一个打印语句和一个递归调用“我们的自我”。在生成器的情况下,对 print 函数的调用变成了 yield 语句,而对 self 的递归调用变成了 yield from "our self"。
  • 您“接受了另一个答案并说,“非常棒。”大概您理解了解释。如之前的评论中所述,yield from 是一种替代的简化语法为您原来的for datum in get_reverse_iterator(head.next): yield datum。我认为它与我在打印示例中的递归调用更接近,除了语法更简单。
【解决方案2】:

每当您将该方法作为生成器调用时(例如for x in get_reverse_iterator()),python 就会开始逐行执行该方法。每当它碰到yield 时,它就会停止冷却并返回它。当它在for 循环的下一次迭代中被要求提供next() 值时,它会继续执行。

这看起来像是一个相当简单的链表遍历习语,其中列表的每个元素都包含本身就是一个列表的数据(或其他一些可迭代的值,如字符串):

list[0].data = [1, 2, 3, 4]
list[1].data = [5, 6, 7, 8]
...
list[9].data = [37, 38, 39, 40]

所以代码在 here 所做的是将这些子列表从主列表的后面打印到主列表的前面。输出应如下所示:

37 38 39 40 33 34 35 36 ... 5 6 7 8 [1, 2, 3, 4]

当您查看代码的执行方式时,这一点变得显而易见。我会用文字重写它:

func get_reverse_iterator(head) {
    if head isn't the last element of the list, then
        call this function on the next element of the list (head.next)
        for every element in the return value of that,
            yield that element
    yield this element's data

“基本情况”是列表的最后一个元素,没有.next。所以它的data,它是可迭代的,被返回到倒数第二个元素。倒数第二个元素依次生成该数据的每个元素,然后将其自己的数据返回给倒数第三个元素。倒数第三个元素依次产生该数据的每个元素,依此类推,直到最终到达列表的第一个元素。到目前为止,每条yield 语句都递归地向上传递了一个元素,因此第一个元素的内部for 循环到目前为止已经产生了36 个值。最后,列表中所有后面的元素都完成了传递值,因此第一个元素到达函数的最后一条语句并产生自己的数据。

但是没有什么可以捕获产生的数据并按单个元素解析它,因此它首先打印为list。或者,至少,对于我上面介绍的示例而言。


在您的情况下,它更简单,因为当您遍历 string 时,每个项目仍然是 string。但在较小的范围内也是如此:

  1. lst的根节点上调用get_reverse_iterator()
  2. 根节点(我称之为NodeA)有一个.next
  3. 在下一个节点上调用get_reverse_iterator(),我将调用它NodeB
  4. NodeB 有一个.next
  5. 在下一个节点上调用get_reverse_iterator(),我将调用它NodeC
  6. NodeC 没有 .next
  7. get_reverse_iterator(NodeC) 跳过for 循环并产生NodeC.data,即'c'`
  8. get_reverse_iterator(NodeB)for 循环内捕获 'c' 并生成它
  9. get_reverse_iterator(NodeA)for 循环内捕获 'c' 并生成它
  10. 'c' 被分配给x,并被打印出来。
  11. 外循环的下一次迭代发生,执行返回get_reverse_iterator(NodeB)
  12. for 循环结束,因为 get_reverse_iterator(NodeC) 已停止产生东西
  13. get_reverse_iterator(NodeB) 结束for 循环,退出if 块,最后产生NodeB.data,即'b'
  14. get_reverse_iterator(NodeA)for 循环中捕获 'b' 并产生它
  15. 'b' 被分配给 x,并被打印出来。
  16. 外循环的下一次迭代发生,执行返回get_reverse_iterator(NodeA)
  17. for 循环结束,因为 get_reverse_iterator(NodeC) 已停止产生东西
  18. get_reverse_iterator(NodeA) 结束for 循环,退出if 块,最后产生NodeA.data,即'a'
  19. 'a' 被分配给 x,并被打印出来
  20. 外部for 循环结束,因为get_reverse_iterator(NodeA) 已停止产生东西。

【讨论】:

  • 太棒了!这总结了我所需要的一切!一件小事 - 在第 12/13 步中,你能解释为什么 for 循环结束并退出 if 块吗?
  • python 中的for 循环一直持续到不再有“下一个元素”为止。在第 12 步中,get_reverse_iterator(NodeB) 要求 get_reverse_iterator(NodeC) 的下一个值。但是由于get_reverse_iterator(NodeC) 已经完全执行完毕,没有下一个值可以产生,所以get_reverse_iterator(NodeB) 决定for 循环结束并继续前进。因为这是if块中唯一的东西,所以它退出了进程中的if块——下一行要执行的代码是函数中的最后一行。
猜你喜欢
  • 1970-01-01
  • 2021-05-15
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多