以不同的顺序生成 itertools.product
Posted
技术标签:
【中文标题】以不同的顺序生成 itertools.product【英文标题】:Generate itertools.product in different order 【发布时间】:2017-02-17 02:10:42 【问题描述】:我有一些排序/评分的参数列表。我想生成可能的参数组合(笛卡尔积)。但是,如果参数的数量很大,这个很快(很快!!)就会变成一个很大的数字。基本上,我想做一个笛卡尔积,但要尽早停止。
import itertools
parameter_options = ['1234',
'123',
'1234']
for parameter_set in itertools.product(*parameter_options):
print ''.join(parameter_set)
生成:
111
112
113
114
121
122
123
124
131
132
133
134
...
我想生成(或类似的东西):
111
112
121
211
122
212
221
222
...
因此,如果我提前停止,我至少会得到几组“好”的参数,其中一组好的参数大多来自列表的早期。这个特定的顺序很好,但我对任何改变“下一个排列”选择顺序的技术感兴趣。我希望生成的早期结果中的大多数项目都位于列表的前面,但并不真正关心解决方案是先生成 113 还是 122,还是先生成 211 或 112。
我的计划是在生成一些排列后停止(可能是 10K 左右?取决于结果)。因此,如果少于截止值,则最终都应该生成。并且最好每个只生成一次。
【问题讨论】:
你到底想要什么?那个特定的顺序,还是其他顺序? 那个特定的顺序会很好,但我对任何改变“下一个排列”选择顺序的技术感兴趣。我希望生成的早期结果具有列表前面的大多数项目,但并不真正关心解决方案是先生成 113 还是 122,还是先生成 211 或 112。 你最终做了什么? 【参考方案1】:如果您根据输出空间的图遍历来考虑输出,我认为您可以按照您想要的顺序获得结果。您需要最近优先遍历,而 itertools.product
函数是深度优先遍历。
试试这样的:
import heapq
def nearest_first_product(*sequences):
start = (0,)*len(sequences)
queue = [(0, start)]
seen = set([start])
while queue:
priority, indexes = heapq.heappop(queue)
yield tuple(seq[index] for seq, index in zip(sequences, indexes))
for i in range(len(sequences)):
if indexes[i] < len(sequences[i]) - 1:
lst = list(indexes)
lst[i] += 1
new_indexes = tuple(lst)
if new_indexes not in seen:
new_priority = sum(index * index for index in new_indexes)
heapq.heappush(queue, (new_priority, new_indexes))
seen.add(new_indexes)
示例输出:
for tup in nearest_first_product(range(1, 5), range(1, 4), range(1, 5)):
print(tup)
(1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(2, 1, 1)
(1, 2, 2)
(2, 1, 2)
(2, 2, 1)
(2, 2, 2)
(1, 1, 3)
(1, 3, 1)
(3, 1, 1)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
(2, 2, 3)
(2, 3, 2)
(3, 2, 2)
(1, 3, 3)
(3, 1, 3)
(3, 3, 1)
(1, 1, 4)
(2, 3, 3)
(3, 2, 3)
(3, 3, 2)
(4, 1, 1)
(1, 2, 4)
(2, 1, 4)
(4, 1, 2)
(4, 2, 1)
(2, 2, 4)
(4, 2, 2)
(3, 3, 3)
(1, 3, 4)
(3, 1, 4)
(4, 1, 3)
(4, 3, 1)
(2, 3, 4)
(3, 2, 4)
(4, 2, 3)
(4, 3, 2)
(3, 3, 4)
(4, 3, 3)
(4, 1, 4)
(4, 2, 4)
(4, 3, 4)
通过更改代码中new_priority
的计算,您可以获得一堆略有不同的订单。当前版本使用平方笛卡尔距离作为优先级,但如果您愿意,您可以使用其他值(例如,合并来自序列的值,而不仅仅是索引)。
如果您不太关心(1, 1, 3)
是否在(1, 2, 2)
之前(只要它们都在(1, 1, 2)
、(1, 2, 1)
和(2, 1, 1)
之后),您可能会做广度优先遍历而不是最近优先。这会更简单一些,因为您可以使用常规队列(如collections.deque
)而不是优先级队列。
这种图遍历使用的队列意味着这段代码使用了一些内存。但是,与在将结果整理之前必须预先生成所有结果相比,内存量要少得多。使用的最大内存与结果空间的表面积成正比,而不是其体积。
【讨论】:
我建议对您的代码进行编辑,因为它有一些无法正常运行的小错误。另外值得一提的是,此实现有一个重要限制:此产品功能不能与生成器一起使用,具体而言,它只能与可下标且已定义长度的迭代器一起使用。如果你不太在意内存,可以绕过这个问题,在开头添加这一行sequences = tuple(tuple(seq) for seq in sequences)
@MATINDELAFUENTESAAVEDRA:是的,感谢您的编辑(当我看到它待定时我批准了它)。我已经修复了我正在测试的代码中的大部分错误,但不知何故忘记将修复复制到答案中。您对参数必须是序列而不是可迭代的限制也是正确的。这就是为什么我选择名称sequences
作为参数。如果你真的需要它,可能可以让它与迭代器一起懒惰地工作,但这样做对于这个答案来说工作量太大了。
这是一个很好的方式来看待这个问题,谢谢。【参考方案2】:
您的问题有点模棱两可,但是阅读您的 cmets 和其他答案后,您似乎想要一个笛卡尔积实现来进行广度搜索而不是深度搜索。
最近我有你同样的需求,但也有要求它不会将中间结果存储在内存中。这对我来说非常重要,因为我正在使用大量参数(因此是一个非常大的笛卡尔积),并且任何存储值或进行递归调用的实现都是不可行的。正如您在问题中所说,这似乎也是您的情况。
由于我没有找到满足这个要求的答案,我来到了这个解决方案:
def product(*sequences):
'''Breadth First Search Cartesian Product'''
# sequences = tuple(tuple(seq) for seqin sequences)
def partitions(n, k):
for c in combinations(range(n+k-1), k-1):
yield (b-a-1 for a, b in zip((-1,)+c, c+(n+k-1,)))
max_position = [len(i)-1 for i in sequences]
for i in range(sum(max_position)):
for positions in partitions(i, len(sequences)):
try:
yield tuple(map(lambda seq, pos: seq[pos], sequences, positions))
except IndexError:
continue
yield tuple(map(lambda seq, pos: seq[pos], sequences, max_position))
在速度方面,这个生成器在开始时运行良好,但在最新结果中开始变慢。所以,虽然这个实现有点慢,但它作为一个不使用内存并且不给出重复值的生成器工作。
正如我在@Blckknght 答案中提到的,这里的参数也必须是序列(可下标和长度定义的迭代)。但是你也可以通过取消第一行的注释来绕过这个限制(牺牲一点内存)。如果您使用生成器/迭代器作为参数,这可能会很有用。
希望我对您有所帮助,如果这对您的问题有帮助,请告诉我。
【讨论】:
【参考方案3】:这个解决方案可能不是最好的,因为它会短暂地将每个组合强制到内存中,但它确实有效。大型数据集可能需要一点时间。
import itertools
import random
count = 100 # the (maximum) amount of results
results = random.sample(list(itertools.product(*parameter_options)), count)
for parameter_set in results:
print "".join(parameter_set)
这将为您提供随机顺序的产品列表。
【讨论】:
好吧,OP 有时希望最终处理所有这些,所以我可能会选择random.shuffle
,而不是random.sample
,所以你仍然有详尽的数据。也就是说,如果输入的大小完全增长,将其全部保存在内存中可能是一个主要问题。
我不明白他在哪里说的。但如果他想这样做——那么到时候他最好按顺序进行。
OP 说“所以如果我早点停下来,......”(强调)。听起来像是他们想要运行的处理,直到他们点击Ctrl-C
或其他东西,生成结果直到他们完成或杀死它。
我能理解这种解释。我认为它更像是“如果我让程序这样做”。希望 OP 会尽快发表评论,以便我们做出澄清。
我绝对不想生成所有结果。如果只有 20 个参数,每个参数只有 2 个选项,那将是 >100 万个元素。以上是关于以不同的顺序生成 itertools.product的主要内容,如果未能解决你的问题,请参考以下文章