我正在寻找一种在Python中执行此操作的简洁方法:
假设我有两个迭代器“iter1”和“iter2”:也许是素数生成器和itertools.count()。我先验地知道两者都是无限的并且单调递增。现在我想对两个args“op”(可能是operator.add或operator.mul)进行一些简单的操作,并使用每个元素计算第一个迭代器的每个元素接下来,使用所述操作,然后一次一个地产生它们,进行分类。显然,这本身就是一个无限的序列。 (正如@RyanThompson在评论中所提到的:这将被称为这些序列的Cartesian Product ......或者更准确地说,是该产品的1d类型。)
最好的方法是:
允许简化假设:
也允许:
我能想到这样做的唯一方法是一种“对角化”过程,在这里我会保留越来越多的部分处理的迭代,并且“向前看”所有可能的next()值的最小值,并且产量。但是,即使在我开始对代码进行编码之前,这种古怪的聚集和一堆deques似乎都是异乎寻常的。
请:不根据我的例子中提到的primes或count()这个事实的答案....我对这个概念有几个用途,与素数和计数无关( )。
更新: OMG!多么棒的讨论!并通过非常彻底的解释得到一些很好的答案。非常感谢。 StackOverflow摇滚;你们摇滚吧。
我将尽快深入研究每个答案,并给出示例代码。从我到目前为止所读到的内容来看,我最初的怀疑是确认没有“简单的Python成语”来做到这一点。相反,通过这种或那种方式,我无法避免无限期地保持iter1和iter2的所有产生的值。
FWIW:如果您想尝试解决方案,这是一个官方的“测试案例”。
import operator
def powers_of_ten():
n = 0
while True:
yield 10**n
n += 1
def series_of_nines():
yield 1
n = 1
while True:
yield int("9"*n)
n += 1
op = operator.mul
iter1 = powers_of_ten()
iter2 = series_of_nines()
# given (iter1, iter2, op), create an iterator that yields:
# [1, 9, 10, 90, 99, 100, 900, 990, 999, 1000, 9000, 9900, 9990, 9999, 10000, ...]
答案 0 :(得分:5)
import heapq
import itertools
import operator
def increasing(fn, left, right):
"""
Given two never decreasing iterators produce another iterator
resulting from passing the value from left and right to fn.
This iterator should also be never decreasing.
"""
# Imagine an infinite 2D-grid.
# Each column corresponds to an entry from right
# Each row corresponds to an entry from left
# Each cell correspond to apply fn to those two values
# If the number of columns were finite, then we could easily solve
# this problem by keeping track of our current position in each column
# in each iteration, we'd take the smallest value report it, and then
# move down in that column. This works because the values must increase
# as we move down the column. That means the current set of values
# under consideration must include the lowest value not yet reported
# To extend this to infinite columns, at any point we always track a finite
# number of columns. The last column current tracked is always in the top row
# if it moves down from the top row, we add a new column which starts at the top row
# because the values are increasing as we move to the right, we know that
# this last column is always lower then any columns that come after it
# Due to infinities, we need to keep track of all
# items we've ever seen. So we put them in this list
# The list contains the first part of the incoming iterators that
# we have explored
left_items = [next(left)]
right_items = [next(right)]
# we use a heap data structure, it allows us to efficiently
# find the lowest of all value under consideration
heap = []
def add_value(left_index, right_index):
"""
Add the value result from combining the indexed attributes
from the two iterators. Assumes that the values have already
been copied into the lists
"""
value = fn( left_items[left_index], right_items[right_index] )
# the value on the heap has the index and value.
# since the value is first, low values will be "first" on the heap
heapq.heappush( heap, (value, left_index, right_index) )
# we know that every other value must be larger then
# this one.
add_value(0,0)
# I assume the incoming iterators are infinite
while True:
# fetch the lowest of all values under consideration
value, left_index, right_index = heapq.heappop(heap)
# produce it
yield value
# add moving down the column
if left_index + 1 == len(left_items):
left_items.append(next(left))
add_value(left_index+1, right_index)
# if this was the first row in this column, add another column
if left_index == 0:
right_items.append( next(right) )
add_value(0, right_index+1)
def fib():
a = 1
b = 1
while True:
yield a
a,b = b,a+b
r = increasing(operator.add, fib(), itertools.count() )
for x in range(100):
print next(r)
答案 1 :(得分:4)
将序列定义为:
a1 <= a2 <= a3 ...
b1 <= b2 <= b3 ...
简而言之a1b1
表示op(a1,b1)
。
根据您允许的假设(非常重要),您知道以下内容:
max(a1, b1) <= a1b1 <= a1b2 <= a1b3 ...
<=
max(a2, b1) <= a2b1 <= a2b2 <= a2b3 ...
<=
max(a3, b1) <= a3b1 <= a3b2 <= a3b3 ...
. .
. .
. .
您必须执行以下操作:
生成a1b1
。您知道,如果继续增加b
变量,则只能获得更高的值。现在的问题是:通过增加a
变量是否有较小的值?您的下限为min(a1, b1)
,因此您必须将a
值增加到min(ax,b1) >= a1b1
。一旦达到这一点,您就可以找到anb1
1 <= n <= x
所在的最小值并安全地获得该值。
然后,您将拥有多个水平链,您必须跟踪它们。每当您的值超过min(ax,b1)
时,您必须增加x
(添加更多链),直到min(ax,b1)
大于安全发送它之前的a1bn
。
只是一个起点...我现在没有时间对其进行编码。
编辑:哦,嘿,这正是你已经拥有的。好吧,没有更多的信息,这就是你所能做的一切,因为我非常确定在数学上,这是必要的。 EDIT2:至于您的“可接受”解决方案:您可以按n
的递增顺序生成min(a1,b1)
,将N
作为op(a,c)
= P返回。你需要更加具体。你说的好像你有一个你通常想看的东西的启发式,你想要通过两个迭代进行的一般方式,但没有告诉我们它是什么我不知道如何做得更好。
更新:温斯顿很好,但假设海报没有提到:op(b,c)
&gt; b>a
如果op(a,b)>=a
。但是,我们知道op(a,b)>=b
和def increasing(fn, left, right):
left_items = [next(left)]
right_items = [next(right)]
#columns are (column value, right index)
columns = [(fn(left_items[0],right_items[0]),0)]
while True:
#find the current smallest value
min_col_index = min(xrange(len(columns)), key=lambda i:columns[i][0])
#generate columns until it's impossible to get a smaller value
while right_items[0] <= columns[min_col_index][0] and \
left_items[-1] <= columns[min_col_index][0]:
next_left = next(left)
left_items.append(next_left)
columns.append((fn(next_left, right_items[0]),0))
if columns[-1][0] < columns[min_col_index][0]:
min_col_index = len(columns)-1
#yield the smallest value
yield columns[min_col_index][0]
#move down that column
val, right_index = columns[min_col_index]
#make sure that right value is generated:
while right_index+1 >= len(right_items):
right_items.append(next(right))
columns[min_col_index] = (fn(left_items[min_col_index],right_items[right_index+1]),
right_index+1)
#repeat
。
这是我的解决方案,采取第二个假设,但不是温斯顿采取的。不过,为了代码结构而向他道具:
def pathological_one():
cur = 0
while True:
yield cur
cur += 100
def pathological_two():
cur = 0
while True:
yield cur
cur += 100
lookup = [
[1, 666, 500],
[666, 666, 666],
[666, 666, 666],
[666, 666, 666]]
def pathological_op(a, b):
if a >= 300 or b >= 400: return 1005
return lookup[b/100][a/100]
r = increasing(pathological_op, pathological_one(), pathological_two())
for x in range(15):
print next(r)
对于演示差异的(病态)输入,请考虑:
>>>
1
666
666
666
666
500
666
666
666
666
666
666
1005
1005
1005
温斯顿的答案给出了:
>>>
1
500
666
666
666
666
666
666
666
666
666
666
1005
1005
1005
虽然我给出了:
{{1}}
答案 2 :(得分:2)
所以你基本上想要采用两个单调递增的序列,然后(懒惰地)计算它们之间的乘法(或加法或另一个运算)表,这是一个二维数组。然后,您希望将该二维数组的元素按排序顺序放置并迭代它们。
一般来说,这是不可能的。但是,如果您的序列和操作是这样的,您可以对表的行和列做出某些保证,那么您可以取得一些进展。例如,假设您的序列仅是正整数的单调增加序列,并且该运算是乘法(如您的示例所示)。在这种情况下,我们知道数组的每一行和一列都是单调递增的序列。在这种情况下,您不需要计算整个数组,而只需计算其中的一部分。具体而言,您必须跟踪以下内容:
要计算迭代器中的下一个元素,必须执行以下操作:
这个过程有点复杂,特别注意要计算N值,你必须在最坏的情况下保存一个与 N的平方根成比例的状态量。(编辑:sqrt (N)实际上是最佳情况。)这与典型的生成器形成鲜明对比,典型的生成器只需要恒定的空间来遍历其元素,无论长度如何。
总之,您可以在某些假设下执行此操作,并且您可以为其提供类似于生成器的界面,但它不能以“流式”方式完成,因为您需要保存大量状态才能以正确的顺序遍历元素。
答案 3 :(得分:2)
让我先谈谈如何直观地解决这个问题。
因为内联读代码有点乏味,我会介绍一些符号:
iter1
。 i1 0 将代表iter1
的第一个元素。 iter2
。op
运营商通过使用简化假设2,我们知道 i1 0 ※ i2 0 是最小元素将永远从你的最终迭代器中获益。下一个元素是较小的 i1 0 ※ i2 1 和 i1 < sub> 1 ※ i2 0 。
假设 i1 0 ※ i2 1 较小,则会产生该元素。接下来,您将得到较小的 i1 1 ※ i2 0 , i1 1 ※ i2 0 , i1 1 ※ i2 <子> 1 子>
这里有一个图遍历问题。首先,将问题视为一棵树。树的根是 i1 0 ※ i2 0 。此节点及其下面的每个节点都有两个子节点。 i1 x ※ i2 y 的两个孩子如下:一个孩子是 i1 x + 1 ※ i2 y ,另一个孩子是 i1 x ※ i2 y + 1 。基于您的第二个假设,我们知道 i1 x ※ i2 y 小于其两个孩子。
(事实上,正如Ryan在评论中提到的,这是一个有向无环图或DAG。一些“父母”与其他“父”节点共享“孩子”。)
现在,我们需要保留前沿 - 可以接下来返回的节点集合。返回节点后,我们将其子节点添加到边界。要选择要访问的下一个节点(并从新迭代器返回),我们将比较边界中所有节点的值。我们采用具有最小值的节点并返回它。然后,我们再次将其两个子节点添加到边界。如果孩子已经在边境(作为其他父母的子女添加),请忽略它。
因为您主要对节点的值感兴趣,所以存储按值索引的这些节点是有意义的。因此,使用dict
可能符合您的利益。此dict中的键应该是节点的值。此dict中的值应该是包含单个节点的列表。因为节点中唯一的标识信息是一对操作数,所以可以将各个节点存储为两元组的操作数。
在实践中,经过几次迭代后,您的边界可能如下所示:
>>> frontier
{1: [(2, 3), (2, 4)], 2: [(3, 5), (5, 4)], 3: [(1, 6)], 4: [(6, 3)]}
因为迭代器不支持随机访问,所以您需要挂起前两个迭代器生成的值,直到不再需要它们为止。 如果边界中的任何值引用了值,您就会知道仍然需要一个值。一旦边界参考值中的所有节点稍后/大于您存储的值,您就会知道不再需要值。例如,当您的边界中的节点仅引用 i1 21 ,时,不再需要 i1 20 i1 25 , i1 33 ,...
如Ryan所述,每个迭代器的每个值都将被无限次使用。因此,每个产生的价值都需要保存。
不幸的是,为了确保元素仅以递增的顺序返回,边界将无限制地增长。您的memoized值可能也会占用大量空间也会不受限制地增长。这可能是你可以通过使问题不那么普遍来解决的问题,但这应该是一个很好的起点。
答案 4 :(得分:0)
使用generators,它们只是作为产生结果的函数编写的迭代器。在这种情况下,您可以编写iter1
和iter2
的生成器以及另一个生成器来包装它们并生成结果(或者使用它们进行计算,或者结果的历史记录)。
从我对这个问题的阅读中你想要这样的东西,它将使用所述操作计算第一个迭代器的每个元素,并使用所述操作,你还要说明你想要一些方法汇总“iter1”,“iter2”和“op”在一个迭代中,它本身产生单调增加输出的值。我建议发电机为这种问题提供简单的解决方案。
import itertools
def prime_gen():
D, q = {}, 2
while True:
if q not in D:
yield q
D[q * q] = [q]
else:
for p in D[q]:
D.setdefault(p + q, []).append(p)
del D[q]
q += 1
def infinite_gen(op, iter1, iter2):
while True:
yield op(iter1.next(), iter2.next())
>>> gen = infinite_gen(operator.mul, prime_gen(), itertools.count())
>>> gen.next()
<<< 0
>>> gen.next()
<<< 3
>>> gen.next()
<<< 10
生成器提供了很大的灵活性,因此编写iter1
和iter2
作为生成器应该相当容易,这些生成器按照您想要的顺序返回所需的值。您还可以考虑使用coroutines,它允许您将值发送到生成器。
答案 5 :(得分:0)
在其他答案中的讨论观察到,无论算法是什么,都可能存在无限存储空间,因为每个新a[n]
必须保持每个b[n]
可用。如果你删除了输入是两个迭代器的限制,而只是要求它们是序列(可索引或只是可以重复重新生成的东西),那么我相信你所有的状态突然崩溃到一个数字:您返回的最后一个值。知道了最后的结果值,您可以搜索输出空间以查找下一个结果值。 (如果要正确发出重复项,则可能还需要跟踪返回结果的次数)
使用一对序列,您有一个简单的递归关系:
result(n) = f(seq1, seq1, result(n-1))
其中f(seq1, seq1, p)
在输出空间q
中搜索q > p
的最小值。实际上,您可能会使序列记忆功能并选择您的搜索算法,以避免破坏已记忆项目池。