【问题标题】:Calling order in recursive generator?递归生成器中的调用顺序?
【发布时间】:2016-03-03 20:22:43
【问题描述】:
def powerset(seq):
    """
    Returns all the subsets of this set. This is a generator.
    """
    if len(seq) <= 1:
        yield seq
        yield []
    else:
        for item in powerset(seq[1:]):
            yield [seq[0]]+item
            yield item

上面是一个递归生成器,可以生成所有幂集。例如,

powerset([1,2,3])=>[1, 2, 3][2, 3][1, 3][3][1, 2][2][1][]

但我对它的工作原理感到困惑。看来它是按这个顺序产生的:

powerset([1,2,3])=>powerset([2,3])=>powerset([3]) 

which is outside=>inside,与我理解的“递归”含义相反,inside=>outside,例如递归求解阶乘(5):

factorial has been called with n = 5
factorial has been called with n = 4
factorial has been called with n = 3
factorial has been called with n = 2
factorial has been called with n = 1
intermediate result for  2  * factorial( 1 ):  2
intermediate result for  3  * factorial( 2 ):  6
intermediate result for  4  * factorial( 3 ):  24
intermediate result for  5  * factorial( 4 ):  120
120

那么如何理解递归生成器呢?

【问题讨论】:

  • 递归意味着调用自身。 powerset 在自身内部调用 powerset,所以它是递归的。
  • @zondo 是的,我很困惑的是,为什么首先调用最外面的产量?
  • 具有递归功能的函数像 stack 一样工作,所以第一个调用将最后输出
  • 对于您的示例,产生的第一件事是[1] + 产生的第一件事是powerset([2, 3])powerset([2, 3]) 首先产生 [2] + powerset([3])。到目前为止,我们的第一个收益是[1] + [2] + powerset([3])。这可以简化为[1, 2] + powerset([3])powerset([3]) 首先产生 [3],因为 len(seq) &lt;= 1。因此,产生的第一件事是[1, 2, 3]
  • @zondo 那么 powerset([3]) 会被调用几次吗?

标签: python recursion generator


【解决方案1】:

让我们看看:

这是递归部分:

for item in powerset(seq[1:]):
            yield [seq[0]]+item
            yield item

基本上这意味着

for every result in a smaller powerset:

    return the not used value + the result

    then return result alone

如果我们看一下 powerset([3]);这首先返回 [3] 然后返回 [] 因为它的长度为 1

powerset([2,3]) 调用 powerset([3]) 然后执行:

  1. 返回 2 和 第一个项的 powerset([3]) (=[2,3])
  2. 只返回 第一个项的 powerset([3]) (=[3])
  3. 返回 2 和 第二个 项的 powerset([3]) (=[2])
  4. 返回 第二个项 powerset([3]) (=[])

这导致[2,3] [3] [2] []

如果我们调用 powerset([1,2,3]) 会发生同样的事情

  1. 1+[2,3]
  2. [2,3]
  3. 1+[3]
  4. [3]
  5. 1+[2]
  6. [2]
  7. 1+[]
  8. []

【讨论】:

    【解决方案2】:

    基本上,您的生成器实现了回溯算法:在到达任何yield 之前,已经进行了许多递归调用。程序文本中首先出现的 yield 调用只有在递归嵌套足够深以使剩余列表的长度为 1 时才会进行,并且只有在此之后才会进入 for 循环,因为这需要最里面的生成器来产生一些东西。

    powerset([1,2,3]) level 1: seq1=[1,2,3]
    | powerset([2,3]) level 2: seq2=[2,3]
    | | powerset([3]) level 3: seq3=[3]
    | | | yield [3]   3: yield seq3, becomes item2 in level 2
    | | yield [2,3]   2: yield [seq2[0]] + item2, becomes item1 in level 1
    | yield [1,2,3]   1: yield [seq1[0]] + item1, output
    | yield [2,3]     1: yield item1, output
    ...
    

    只有在for 循环的最后一行“冒泡”时,最内层生成的短序列才会出现在最外层调用的输出中。在此之前,会产生与seq[0] 的组合。

    如果您想反过来输出,请尝试在循环内交换两个 yield 行。

    【讨论】:

    • '...这需要最里面的生成器来产生一些东西。'那么为什么先输出[1,2,3]和[2,3]而不是最里面的yield[3]和[]?
    • 查看我在生成值“冒泡”的时间线上的尝试。
    猜你喜欢
    • 2019-12-20
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    • 2016-10-12
    • 2020-02-09
    • 2013-11-19
    相关资源
    最近更新 更多