无限生成器的Python乘积

无限生成器的Python乘积,python,python-3.x,generator,itertools,Python,Python 3.x,Generator,Itertools,我试图得到两个无限生成器的乘积,但是itertools中的product函数是一种行为 示例行为: from itertools import * i = count(1) j = count(1) x = product(i, j) [Killed] 我想要的是: x = product(i, j) ((0,0), (0,1), (1,0), (1,1) ...) 组合返回的顺序无关紧要,只要给定无限时间,所有组合最终都会生成。这意味着给定元素的组合,返回的生成器中必须有一个具有该组合

我试图得到两个无限生成器的乘积,但是
itertools
中的
product
函数是一种行为

示例行为:

from itertools import *
i = count(1)
j = count(1)
x = product(i, j)

[Killed]
我想要的是:

x = product(i, j)

((0,0), (0,1), (1,0), (1,1) ...)
组合返回的顺序无关紧要,只要给定无限时间,所有组合最终都会生成。这意味着给定元素的组合,返回的生成器中必须有一个具有该组合的有限索引。

tl;博士 下面给出的代码现在包含在PyPI上的包中。因此,在运行此操作之前,现在您实际上可以
pip install infinite

from itertools import count
from infinite import product

for x, y in product(count(0), count(0)):
    print(x, y)
    if (x, y) == (3, 3):
        break

惰性解决方案 如果您不关心顺序,因为生成器是无限的,有效输出将是:

(a0, b1), (a0, b2), (a0, b3), ... (a0, bn), ...
因此,您可以从第一个生成器获取第一个元素,然后在第二个生成器上循环

如果您真的想这样做,您需要一个嵌套的循环,并且您需要使用
tee
复制嵌套的生成器,否则您将无法再次在其上循环(即使这并不重要,因为您将永远不会耗尽生成器,因此您将永远不需要循环)

但如果你真的想这么做,这里有:

from itertools import tee

def product(gen1, gen2):
    for elem1 in gen1:
        gen2, gen2_copy = tee(gen2)
        for elem2 in gen2_copy:
            yield (elem1, elem2)
我们的想法是始终制作一份
gen2
。首先尝试使用有限的生成器

print(list(product(range(3), range(3,5))))
[(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4)]
然后是无限的:

print(next(product(count(1), count(1))))
(1, 1)

zig-zag算法 正如其他人在评论中指出的(以及在前面的解决方案中所述),这不会产生所有的组合。然而,自然数和数对之间的映射是存在的,因此必须能够以不同的方式迭代这些数对,以便在有限的时间内查找特定的数对(没有无穷多个数),您需要锯齿扫描算法

为此,您需要缓存以前的值,因此我创建了一个类
GenCacher
,以缓存以前提取的值:

class GenCacher:
    def __init__(self, generator):
        self._g = generator
        self._cache = []

    def __getitem__(self, idx):
        while len(self._cache) <= idx:
            self._cache.append(next(self._g))
        return self._cache[idx]

例子 这将产生以下输出:

0 0
0 1
1 0
2 0
1 1
0 2
0 3
1 2
2 1
3 0
4 0
3 1
2 2

将解决方案扩展到2台以上的发电机 我们可以对解决方案进行一些编辑,使其即使适用于多个生成器。基本思想是:

  • 迭代距离
    (0,0)
    (索引之和)
    (0,0)
    是唯一距离为0的,
    (1,0)
    (0,1)
    位于距离1处,以此类推

  • 生成该距离的所有索引元组

  • 提取相应的元素

  • 我们仍然需要
    GenCacher
    类,但代码变成:

    def summations(sumTo, n=2):
        if n == 1:
            yield (sumTo,)
        else:
            for head in xrange(sumTo + 1):
                for tail in summations(sumTo - head, n - 1):
                    yield (head,) + tail
    
    def product(*gens):
        gens = map(GenCacher, gens)
    
        for dist in count(0):
            for idxs in summations(dist, len(gens)):
                yield tuple(gen[idx] for gen, idx in zip(gens, idxs))
    
    带有
    itertools.tee
    的自制解决方案。由于中间状态存储在
    tee

    这将有效地返回不断扩展的正方形的边:

    0 1 4 9 
    2 3 5 a
    6 7 8 b
    c d e f
    

    给定无限的时间和无限的内存,这个实现应该返回所有可能的产品。

    无论你怎么做,内存都会增长很多,因为每个迭代器中的每个值在第一次之后都会出现无限次,所以它必须保持在某个不断增长的变量中

    因此,类似这样的方法可能会奏效:

    def product(i, j):
        """Generate Cartesian product i x j; potentially uses a lot of memory."""
        earlier_values_i = []
        earlier_values_j = []
    
        # If either of these fails, that sequence is empty, and so is the
        # expected result. So it is correct that StopIteration is raised,
        # no need to do anything.
        next_i = next(i)
        next_j = next(j)
        found_i = found_j = True
    
        while True:
            if found_i and found_j:
                yield (next_i, next_j)
            elif not found_i and not found_j:
                break  # Both sequences empty
    
            if found_i: 
                for jj in earlier_values_j:
                    yield (next_i, jj)
            if found_j:
                for ii in earlier_values_i:
                    yield (ii, next_j)
    
            if found_i:
                earlier_values_i.append(next_i)
            if found_j:
                earlier_values_j.append(next_j)
    
            try:
                next_i = next(i)
                found_i = True
            except StopIteration:
                found_i = False
    
            try:
                next_j = next(j)
                found_j = True
            except StopIteration:
                found_j = False
    

    这在我的脑海中是如此简单,但在打印出来后看起来非常复杂,一定有更简单的方法。但我认为它会起作用。

    您可以使用发电机表达式:

    from itertools import *
    i = count(1)
    j = count(1)
    
    for e in ((x, y) for x in i for y in j):
        yield r
    
    或者在python3中:

    yield from ((x, y) for x in i for y in j)
    

    这不起作用,因为并非所有的组合最终都会生成。在任何情况下,它们最终都不会生成。你正在处理无限的问题。您应该指定顺序,否则任何解决方案都是可以接受的。我建议您采用之字形顺序。我尝试过,但这需要无限次地复制生成器,而
    itertools.tee
    似乎无法做到这一点do@muddyfish我添加了最终列出它们的代码。@all检查编辑,我实现了zig-zag算法,现在它可以正常工作了。你可能会对
    椰子郎
    感兴趣。寻找一个与您想要的类似的示例。它不会递增
    x
    ,因此即使给定无穷大,也不会生成所有组合time@muddyfish,该行为未在问题中指定,您真正想要实现的是什么?编辑的问题。这样更好吗?
    def product(i, j):
        """Generate Cartesian product i x j; potentially uses a lot of memory."""
        earlier_values_i = []
        earlier_values_j = []
    
        # If either of these fails, that sequence is empty, and so is the
        # expected result. So it is correct that StopIteration is raised,
        # no need to do anything.
        next_i = next(i)
        next_j = next(j)
        found_i = found_j = True
    
        while True:
            if found_i and found_j:
                yield (next_i, next_j)
            elif not found_i and not found_j:
                break  # Both sequences empty
    
            if found_i: 
                for jj in earlier_values_j:
                    yield (next_i, jj)
            if found_j:
                for ii in earlier_values_i:
                    yield (ii, next_j)
    
            if found_i:
                earlier_values_i.append(next_i)
            if found_j:
                earlier_values_j.append(next_j)
    
            try:
                next_i = next(i)
                found_i = True
            except StopIteration:
                found_i = False
    
            try:
                next_j = next(j)
                found_j = True
            except StopIteration:
                found_j = False
    
    from itertools import *
    i = count(1)
    j = count(1)
    
    for e in ((x, y) for x in i for y in j):
        yield r
    
    yield from ((x, y) for x in i for y in j)