加权元素的笛卡尔积

问题描述 投票:1回答:2

我有一组元素集合,其中每个元素都附加一个值(0..1)(实际容器类型无关紧要)。我正在迭代笛卡尔积,即从每个集合中取出一个元素的元素组合,如下所示:

import random
import itertools

stuff = [[random.random() for _ in range(random.randint(2,3))] for _ in range(2)]

for combo in itertools.product(*stuff):
    print sum(combo)  # yield in actual application

很容易,但我希望首先得到具有更高总和值的组合。这不需要是确定性的,对于我来说,在低价值组合之前获得高价值组合的机会就足够了。

如果没有先创建所有组合,是否有一种聪明的方法可以做到这一点?也许通过以某种方式对元素集进行排序/移位?

python heuristics cartesian-product
2个回答
2
投票

确实有一种更好的方法,首先按降序对集合进行排序,然后迭代,以便首先选择每个集合的初始元素。由于它们已经分类,这确保我们通常首先获得高价值组合。

让我们分步建立我们的直觉,一路绘制结果。我发现这对理解该方法有很大帮助。

目前的方法

首先,您当前的方法(为了清晰起见,轻轻编辑)。

import random
import itertools
import matplotlib.pyplot as plt

list1 = [random.random() for _ in range(50)]
list2 = [random.random() for _ in range(50)]

values = []

for combo in itertools.product(list1, list2):
    values.append(sum(combo))
    print(sum(combo))           # yield in actual application

plt.plot(values)
plt.show()

导致,

Current method

那就是到处都是!我们已经可以通过强加一些排序结构来做得更好。让我们接下来探讨一下。

预先排序列表

list1 = [random.random() for _ in range(50)]
list2 = [random.random() for _ in range(50)]

list1.sort(reverse=True)
list2.sort(reverse=True)

for combo in itertools.product(list1, list2):
    print(sum(combo))           # yield in actual application

哪个收益率,

Pre-sorted lists

看看那美女的结构!我们可以利用它来首先产生最大的元素吗?

利用结构

对于这部分,我们将不得不放弃itertools.product,因为它太符合我们的口味。类似的功能很容易编写,我们可以在我们这样做的时候利用数据的规律性。我们对图2中的峰值了解多少?好吧,由于数据已经排序,它们必须全部以较低的索引出现。如果我们将我们集合的索引想象成一些更高维度的空间,这意味着我们需要更喜欢接近原点的点 - 至少在最初阶段。

以下2-D数字支持我们的直觉,

Index structure of the solution space

基于图形的遍历矩阵应该足够了,确保我们每次都移动到一个新元素。现在,我将在下面提供的实现确实构建了一组访问节点,这不是您想要的。幸运的是,可以删除不在“边界”(当前可到达但未访问的节点)上的所有被访问节点,这将极大地限制空间复杂性。我把它留给你想出一个聪明的方法。

代码,

import random
import itertools
import heapq


def neighbours(node):       # see https://stackoverflow.com/a/45618158/4316405
    for relative_index in itertools.product((0, 1), repeat=len(node)):
        yield tuple(i + i_rel for i, i_rel
                    in zip(node, relative_index))


def product(*args):
    heap = [(0, tuple([0] * len(args)))]    # origin
    seen = set()

    while len(heap) != 0:                   # while not empty
        idx_sum, node = heapq.heappop(heap)

        for neighbour in neighbours(node):
            if neighbour in seen:
                continue

            if any(dim == len(arg) for dim, arg in zip(neighbour, args)):
                continue                    # should not go out-of-bounds

            heapq.heappush(heap, (sum(neighbour), neighbour))

            seen.add(neighbour)

            yield [arg[idx] for arg, idx in zip(args, neighbour)]


list1 = [random.random() for _ in range(50)]
list2 = [random.random() for _ in range(50)]

list1.sort(reverse=True)
list2.sort(reverse=True)

for combo in product(list1, list2):
    print(sum(combo))

代码沿着边界走,每次选择具有最低索引和的索引(对原点的“接近度”的启发式)。这很有效,如下图所示,

Exploiting structure with a graph-walking algorithm


0
投票

在N. Wouda的回答的启发下,我尝试了另一种方法。在测试他们的答案时,我注意到类似n-ary编码的索引中的模式(这里有3组):

...
(1,1,0)
(1,1,1)
(0,0,2)
(0,1,2)
(1,0,2) <- !
(1,1,2)
(0,2,0)
(0,2,1)
(1,2,0)
...

请注意,较低的数字会在较高数字之前增加。所以我在代码中复制了这个模式:

idx = np.zeros((len(args)), dtype=np.int)
while max(idx) < 50:  # TODO stop condition
    yield [arg[i] for arg,i in zip(args,idx)]

    low = np.min(idx)
    imin = np.argwhere(idx == low)
    inxt = np.argwhere(idx == low+1)

    idx[imin[:-1]] = 0  # everything to the left of imin[-1]
    idx[imin[-1]] += 1  # increase the last of the lowest indices
    idx[inxt[inxt > imin[-1]]] = 0  # everything to the right

因为我刚刚测试,我采取了一些快捷方式;结果也不算太糟糕。虽然在一开始这个功能优于N. Wouda的解决方案,但它越长越好。我认为“指数波”的形状不同,导致距离原点更远的指数的噪声更高。

value vs. n'th product有趣!

编辑我认为这很有趣,所以我想象了索引迭代的方式 - JFYI :)

Index wavefront N. Wouda指数波前N. Wouda

Index wavefront from this answer索引wavefront来自这个答案

© www.soinside.com 2019 - 2024. All rights reserved.