无限生成器的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)