【问题标题】:Cartesian Product of Weighted Elements加权元素的笛卡尔积
【发布时间】:2018-08-16 07:53:58
【问题描述】:

我有一个 集合集合 元素,其中每个元素都有一个附加值 (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


    【解决方案1】:

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

    让我们逐步建立我们的直觉,并在此过程中绘制结果。我发现这对理解该方法有很大帮助。

    当前方法

    首先,您当前的方法(为清楚起见,稍作编辑)。

    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()
    

    导致,

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

    对列表进行预排序

    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
    

    哪个产量,

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

    利用结构

    对于这一部分,我们将不得不放弃itertools.product,因为它对我们的口味来说太笼统了。类似的函数很容易编写,我们可以在这样做时利用数据的规律性。我们对图 2 中的峰了解多少?好吧,由于数据是排序的,它们必须都出现在较低的索引处。如果我们将集合的索引想象为某个更高维空间,这意味着我们需要更喜欢靠近原点的点 - 至少在最初是这样。

    下面的二维图支持我们的直觉,

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

    代码,

    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))
    

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

    【讨论】:

    • 哇,我从来没有得到这么彻底的答案!非常感谢 - 这真的很有帮助!
    • 另外,感谢您展示中间步骤和代码 - 它有助于了解您是如何解决问题的 :)
    【解决方案2】:

    受 N. Wouda 回答的启发,我尝试了另一种方法。在测试他们的答案时,我注意到索引中的模式类似于 n 元编码(这里是 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 的解决方案,但运行时间越长越差。我认为“索引波”的形状不同,导致远离原点的索引噪声更高。

    有趣!

    编辑我认为这很有趣,所以我将索引迭代的方式可视化 - JFYI :)

    指数波前 N. Wouda

    来自这个答案的索引波前

    【讨论】:

    • 这似乎是对的。我怀疑如果您在我的方法中使用欧几里得范数 (np.linalg.norm) 作为启发式,而不是索引总和,您可能会得到更好的结果。不过还没有测试过。
    猜你喜欢
    • 2015-07-13
    • 1970-01-01
    • 2020-07-26
    • 1970-01-01
    • 2011-03-24
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多