以不同的顺序生成 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的主要内容,如果未能解决你的问题,请参考以下文章

缺少数据的 2D 图像中的曲线拟合

比较两个文档,父元素和子元素的顺序不同

本地写入是不是以与生成的顺序相同的顺序提交到 firebase

python 以随机顺序生成列表的所有组合的字典

为啥以不同的顺序运行我的测试会导致性能截然不同? [复制]

csharp 用于以所选元素阵列的词典顺序生成所有排列的扩展方法。