【问题标题】:Unexpected behavior in Python recursive generator with izip使用 izip 的 Python 递归生成器中的意外行为
【发布时间】:2016-05-22 02:23:33
【问题描述】:

我编写了一个名为size_subsets 的函数,当传递一个城市列表(数字)时,它返回一定大小的所有子集。但是,使用 izip() 而不是两个 for-yield 块来重述函数会完全破坏这种行为。

下面的第二种方法使用izip() 重述第一种方法,但由于某种原因,它在顶层没有返回任何内容。谁能帮我弄清楚这是为什么?

打印语句显示,在size_subsets_broken 中递归的最底层确实产生了一些(不是全部)正确子集,但由于某种原因,即使这些子集也没有进入顶层。

def size_subsets(cities, size, sofar):
    if not size:
        yield sofar
        return
    elif len(cities) < size:
        return
    else:
        curr_city = cities.pop()
        for a in size_subsets(cities[:], size - 1, sofar | {curr_city}):
            yield a
        for b in size_subsets(cities[:], size, sofar):
            yield b


def size_subsets_broken(cities, size, sofar):
    if not size:
        yield sofar
        return
    elif len(cities) < size:
        return
    else:
        curr_city = cities.pop()
        inclusive = size_subsets_broken(cities[:], size - 1, sofar | {curr_city})
        exclusive = size_subsets_broken(cities[:], size, sofar)

        for incl_subset, excl_subset in izip(inclusive, exclusive):
            yield incl_subset
            yield excl_subset


print list(size_subsets([1, 2, 3], 2, set()))           # [set([2, 3]), set([1, 3]), set([1, 2])]
print list(size_subsets_broken([1, 2, 3], 2, set()))    # []

【问题讨论】:

  • 您的第一个方法产生所有a 值,然后是所有b 值。您的第二种方法在它们之间交替。你想要吗?

标签: python recursion generator subset


【解决方案1】:

我怀疑你误解了izip() 的工作原理。当 shortest 输入迭代用尽时,它就会停止,并且没有理由相信您的 inclusiveexclusive 总是具有相同的长度。

>>> from itertools import izip
>>> for i in izip(range(10), [6]):
...     print i
(0, 6)

>>> for i in izip(range(10), []):
...     print i

请注意,第一个示例中只有一个输出,而第二个示例中根本没有输出。这一切都在意料之中。

详情

size_subsets_broken() 的顶级调用会创建一个生成器-迭代器(gi) 对象。对list() 的调用迫使后者做某事。

它创建 inclusiveexclusive gi 对象,带有参数(忽略 sofar[1, 2], 1[1, 2], 2izip() 然后尝试合并它们。

izip() 首先尝试从inclusive 获取值(最终无法提供任何东西,这就是为什么顶级gi 也从不产生任何东西 - 实际上,它甚至从未尝试强制exclusive产生任何东西,因为 inclusive 是空的 - izip() “当最短的输入迭代用尽时,只是简单地停止”)。

回想一下,顶级inclusive 的参数是[1, 2], 1。它为[1], 0[1], 1 创建gi 对象。 它的 izip() 推送[1], 0 分支以产生一个值,它通过if not size 测试来生成一个值。所以它的izip() 继续推送[1], 1 分支以获得一个值。

这又会创建 [], 0[], 1 gi 分支。 izip() 的另一层推送其中的第一个以产生一个值(再次通过if not size 测试),但由于if len(cities) &lt; size: return,第二个分支没有产生任何值。所以上一级的izip() 放弃了,它所属的gi 什么也没有产生。

向上传播:在每一层,izip() 至少找到一个空迭代器,因此永远不会进入 for ... in izip(...): 循环的主体(在任何层)。

顺便说一句,izip() 没有问题。在此算法中尝试使用izip() 根本没有意义。

【讨论】:

  • 是的,谢谢。在这种情况下,我只是试图耗尽两个发电机,而不是并行迭代它们,直到最短的发电机被耗尽。我试图通过使用izip() 来保存几行代码,但这根本不是izip 的用途。非常感谢!
  • 您可以改用itertools.chain(inclusive, exclusive),它从左到右一次耗尽其输入迭代。
猜你喜欢
  • 2017-07-11
  • 2012-05-03
  • 2016-08-25
  • 2021-08-28
  • 2021-05-22
  • 2016-05-03
  • 2019-06-13
  • 2022-12-12
相关资源
最近更新 更多