我一直在尝试创建 15 个数字的随机列表,从每个可用列表(15 个列表)中仅选取一个数字,并且不重复任何数字。
下面的代码做到了这一点,但它仅限于两个不同的列表。我想摆脱这个限制。
import random
n1 = list(range(1, 5))
n2 = list(range(2, 5))
n3 = list(range(3,6))
n4 = list(range(5,8))
n5 = list(range(6,10))
n6 = list(range(8,12))
n7 = list(range(10,13))
n8 = list(range(11,15))
n9 = list(range(13,17))
n10 = list(range(14,18))
n11 = list(range(16,20))
n12 = list(range(18,21))
n13 = list(range(20,23))
n14 = list(range(22,24))
n15 = list(range(23,25))
for i in range(10):
lista = random.sample(list(zip(n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12,n13,n14,n15)),1)
print(lista)
当你做类似的事情时
zip([1,2,3,4],[5,6,7,8])
结果输出只是对
[(1, 5), (2, 6), (3, 7), (4, 8)]
所以你不会得到像 (1, 6) 或 (2, 5) 这样的可能选项。 如果你真的想做这样的事情,你应该做笛卡尔积,如下所示:
itertools.product([1,2,3,4],[5,6,7,8])
这将为您提供所有可能的组合。 例如:
>>> random.choice(list(itertools.product([1,2,3,4],[5,6,7,8])))
(1, 6)
但是,如果您实际上尝试使用包含超过一两个元素的集合的 15 路笛卡尔积,则它将构造的结果集将是巨大的,并且可能无法容纳在内存中。
此外,如果集合重叠,您必须进行某种过滤以丢弃多次选择相同数字的选项。
获得不重复的随机列表并且从集合中选择每个元素的最简单方法就是逐个元素地选择:
def pick_unique_elements_from_lists(*args):
while True:
result = []
already_chosen = set()
for arg in args:
valid_choices = [ n for n in arg if n not in already_chosen ]
if not valid_choices:
continue
choice = random.choice(valid_choices)
result.append(choice)
already_chosen.add(choice)
return tuple(result)
然而,虽然这里的选择是随机的,但我们可能想知道它们是否会“均匀”随机。 例如,假设我们要选择一个 4 元组,其中第一个元素来自 [1,2],第二个元素来自 [1,3],第三个元素来自 [1,4],第四个元素来自 [1, 5]。 有几种方法可以做到这一点:
(1,3,4,5)pick_unique_elements_from_lists
函数的作用;如果你尝试一下,你会发现大约 50% 的情况下它会给出以 1 开头的元组。
pick_unique_elements_from_lists
函数还有另一个缺点,那就是如果你给它一个参数序列,就不可能从中选择任何不同元素的元组,例如
[1, 2], [2, 3], [1, 3]
然后它会永远旋转,试图找出有效的样本。
如果您需要均匀采样,我可以看到三种方法:
实际上枚举您可以获得的每个可能的元组,然后随机选择其中一个。
def accept_reject_from_lists(*args):
universe = set().union(*args)
found = False
while not found:
candidate = random.sample(universe, len(args))
found = True
for i in range(len(candidate)):
if candidate[i] not in args[i]:
found = False
break
return tuple(candidate)
这仍然有一个缺点,如果没有任何元组满足您的条件,它将进入无限循环,并且它也可能需要extreme
很长的时间,具体取决于列表之间有多少重叠,但有利的一面如果你给它解决一个巨大的问题,它不会耗尽内存并崩溃。
from time import time
from itertools import product
n1 = list(range(1, 5))
n2 = list(range(2, 5))
n3 = list(range(3,6))
n4 = list(range(5,8))
n5 = list(range(6,10))
n6 = list(range(8,12))
n7 = list(range(10,13))
n8 = list(range(11,15))
n9 = list(range(13,17))
n10 = list(range(14,18))
n11 = list(range(16,20))
n12 = list(range(18,21))
n13 = list(range(20,23))
n14 = list(range(22,24))
n15 = list(range(23,25))
npools = (n1,n2,n3,n4,n5,n6,n7,n8,n9,n10,n11,n12,n13,n14,n15)
>>> t=time();sum(1 for i in product(*npools) if len(set(i))==len(i)),time()-t
(1369116, 13.050522804260254)
通过更多的工作,如果我们将池分成不重叠的池(将它们称为总池的一部分),然后在每个部分上使用纯笛卡尔积,并在每个步骤中更新后续部分,我们可以做得更好使用已知值,这样我们就可以避免生成无效的产品。最后,将每个部分的每个产品重新组合在一起(使用所有东西应该去的位置的索引):
parts=[[npools[0]]]
for i in range(1,len(npools)):
for p in parts:
if npools[i][0]>p[-1][-1]:
parts[parts.index(p)].append(npools[i])
break
else:
parts.append([npools[i]])
# location of each pool in the parts
ix=[[npools.index(j) for j in i] for i in parts]
# there are 3 parts; the following could probably be done with some sort of
# recursive function but the steps are spelled here to show the
# process for these 3 parts
t=time()
for i in product(*parts[0]):
s0=dict(zip(i,ix[0]))
for j in product(*[[j for j in p if j not in s0] for p in parts[1]]):
s1 = s0.copy()
s1.update(dict(zip(j,ix[1])))
if len(i)+len(j)==len(s1):
for k in product(*[[j for j in p if j not in s1] for p in parts[2]]):
s2 = s1.copy()
s2.update(dict(zip(k,ix[2])))
if len(i)+len(j)+len(k)==len(s2):
out = [None]*len(s2)
for e,x in s2.items():
out[x]=e
ok+=1
>>> ok, time()-t
(1369116, 4.670477390289307)
如您所见,这只是过滤方法中自然生成的笛卡尔积总数的一小部分(大约 1/35):
>>> from math import prod
>>> prod([len(i) for i in npools])
47775744