我有一组元素的集合,其中每个元素都有一个附加值(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
很容易,但是我想首先获得具有更高总和的组合。这不必是确定性的,对我来说,拥有高价值组合的机会要比低价值组合的机会高得多。
是否有一种聪明的方法可以做到这一点而无需先创建所有组合?也许通过以某种方式对元素集进行排序/移动?
答案 0 :(得分: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()
结果
到处都是!通过施加某种排序的结构,我们已经可以做得更好。接下来让我们探讨一下。
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))
代码每次都沿边界走,每次选择索引总和最低的索引(启发式“接近”原点)。如下图所示,效果很好
答案 1 :(得分:0)
受沃达(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:)