【问题标题】:What actually happens when a recursive function is also a generator?当递归函数也是生成器时,实际上会发生什么?
【发布时间】:2021-12-27 02:18:16
【问题描述】:

我正在编写一个函数,给定一个数组,返回一个可能排列的列表。 我想出了以下方法,效果很好:

def _permutate(elems):
    if len(elems) <= 1:
        yield elems
    else:
        for i in range(len(elems)):
            for perm in _permutate(elems[:i] + elems[i+1:]):
                yield [elems[i]] + perm

所以运行print(list(_permutate([2, 1, 3]))) 给我:[[2, 1, 3], [2, 3, 1], [1, 2, 3], [1, 3, 2], [3, 2, 1], [3, 1, 2]]

但我想知道在后台实际发生了什么... 我知道一个递归函数会添加到一个堆栈中,直到它到达它的基本情况,然后在堆栈中向后工作以给你一个结果,然后一个生成器函数在它的第一个 yield 处暂停计算并返回一个你必须循环到的生成器得到每个产量的结果,但我真的很困惑当你把两者放在一起时会发生什么。


用调试语句重写函数

def _permutate(elems):
    if len(elems) <= 1:
        print(elems) # NEW
        yield elems
    else:
        for i in range(len(elems)):
            for perm in _permutate(elems[:i] + elems[i+1:]):
                print("NEW", [elems[i]], perm)  # NEW
                yield [elems[i]] + perm

然后运行

gen = _permutate([1,2,3,4,5])
print('NEXT', next(gen))
print('NEXT', next(gen))    
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))

更清楚地显示发生了什么:

[5]
NEW [4] [5]
NEW [3] [4, 5]
NEW [2] [3, 4, 5]
NEW [1] [2, 3, 4, 5]
NEXT [1, 2, 3, 4, 5]
[4]
NEW [5] [4]
NEW [3] [5, 4]
NEW [2] [3, 5, 4]
NEW [1] [2, 3, 5, 4]
NEXT [1, 2, 3, 5, 4]
[5]
NEW [3] [5]
NEW [4] [3, 5]
NEW [2] [4, 3, 5]
NEW [1] [2, 4, 3, 5]
NEXT [1, 2, 4, 3, 5]
[3]
NEW [5] [3]
NEW [4] [5, 3]
NEW [2] [4, 5, 3]
NEW [1] [2, 4, 5, 3]
NEXT [1, 2, 4, 5, 3]
[4]
NEW [3] [4]
NEW [5] [3, 4]
NEW [2] [5, 3, 4]
NEW [1] [2, 5, 3, 4]
NEXT [1, 2, 5, 3, 4]
[3]
NEW [4] [3]
NEW [5] [4, 3]
NEW [2] [5, 4, 3]
NEW [1] [2, 5, 4, 3]
NEXT [1, 2, 5, 4, 3]
[5]
NEW [4] [5]
NEW [2] [4, 5]
NEW [3] [2, 4, 5]
NEW [1] [3, 2, 4, 5]
NEXT [1, 3, 2, 4, 5]

找到一篇文章here,其中显示了递归生成器期间发生的事情的表格。 我尝试使用数组 [1,2,3] 重新创建此表

【问题讨论】:

  • 理解的重要一点:递归与调用另一个函数没有什么不同。假设您定义了一个函数_permutate_2,它的代码与_permute 完全相同,并在_permute 中调用了that...它的工作原理就是这样
  • 所以很简单,_permutate(elems[:i] + elems[i+1:])创建一个新的生成器对象,然后迭代

标签: python recursion generator


【解决方案1】:

我做了一个实验来说明生成器的流程是如何工作的:

def _permutate(elems):
    if len(elems) <= 1:
        print(elems) # NEW
        yield elems
    else:
        for i in range(len(elems)):
            for perm in _permutate(elems[:i] + elems[i+1:]):
                print([elems[i]] + perm)  # NEW
                yield [elems[i]] + perm

# NEW
gen = _permutate([1,2,3,4,5])
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))
print('NEXT', next(gen))

输出:

[5]
[4, 5]
[3, 4, 5]
[2, 3, 4, 5]
[1, 2, 3, 4, 5]
NEXT [1, 2, 3, 4, 5]
[4]
[5, 4]
[3, 5, 4]
[2, 3, 5, 4]
[1, 2, 3, 5, 4]
NEXT [1, 2, 3, 5, 4]
[5]
[3, 5]
[4, 3, 5]
[2, 4, 3, 5]
[1, 2, 4, 3, 5]
NEXT [1, 2, 4, 3, 5]
[3]
[5, 3]
[4, 5, 3]
[2, 4, 5, 3]
[1, 2, 4, 5, 3]
NEXT [1, 2, 4, 5, 3]
[4]
[3, 4]
[5, 3, 4]
[2, 5, 3, 4]
[1, 2, 5, 3, 4]
NEXT [1, 2, 5, 3, 4]
[3]
[4, 3]
[5, 4, 3]
[2, 5, 4, 3]
[1, 2, 5, 4, 3]
NEXT [1, 2, 5, 4, 3]
[5]
[4, 5]
[2, 4, 5]
[3, 2, 4, 5]
[1, 3, 2, 4, 5]
NEXT [1, 3, 2, 4, 5]

【讨论】:

  • 谢谢!这使它更清楚一些。那么遇到的第一个产量是在函数的递归部分的基本情况下吗?所以它应该在技术上暂停,但我们有一个 for 循环迭代返回的生成器,这就是为什么我们在“NEXT”打印语句之前看到数组?
  • @Domanowska 老实说,我已经开始写一个关于正在发生的事情的解释,但后来有点迷失了自己。我认为唯一的递归调用是“for perm in _permutate(...)”,我相信这会完全消耗生成器的递归,直到它没有更多的收益,此时我们返回范围。
  • @Domanowska 我认为如果你按照它的逻辑用笔和纸以 [1, 2] 作为输入开始,那么一旦你对此感到满意,尝试使用 [ 0, 1, 2] 代替。
  • 啊,我明白了,我已将您的 print([elems[i]] + perm) 替换为 print("NEW", [elems[i]], perm),我认为它正是如此。
猜你喜欢
  • 2020-11-03
  • 2014-10-02
  • 1970-01-01
  • 1970-01-01
  • 2016-03-17
  • 2019-05-23
  • 1970-01-01
  • 2021-04-03
  • 2015-10-05
相关资源
最近更新 更多