我需要一些列表的独特随机排列,而无需有效替换。我目前的做法:
total_permutations = math.factorial(len(population))
permutation_indices = random.sample(xrange(total_permutations), k)
k_permutations = [get_nth_permutation(population, x) for x in permutation_indices]
其中get_nth_permutation
完全听起来像是有效的(意味着O(N))。但是,这仅适用于len(population) <= 20
,仅仅因为21!非常漫长,xrange(math.factorial(21))
无效:
OverflowError: Python int too large to convert to C long
是否有更好的算法来采样k个唯一排列而无需替换O(N)?
答案 0 :(得分:6)
在某一点上,没有必要使用get_nth_permutation
来获得排列。只是洗牌!
>>> import random
>>> l = range(21)
>>> def random_permutations(l, n):
... while n:
... random.shuffle(l)
... yield list(l)
... n -= 1
...
>>> list(random_permutations(l, 5))
[[11, 19, 6, 10, 0, 3, 12, 7, 8, 16, 15, 5, 14, 9, 20, 2, 1, 13, 17, 18, 4],
[14, 8, 12, 3, 5, 20, 19, 13, 6, 18, 9, 16, 2, 10, 4, 1, 17, 15, 0, 7, 11],
[7, 20, 3, 8, 18, 17, 4, 11, 15, 6, 16, 1, 14, 0, 13, 5, 10, 9, 2, 19, 12],
[10, 14, 5, 17, 8, 15, 13, 0, 3, 16, 20, 18, 19, 11, 2, 9, 6, 12, 7, 4, 1],
[1, 13, 15, 18, 16, 6, 19, 8, 11, 12, 10, 20, 3, 4, 17, 0, 9, 5, 2, 7, 14]]
对于len(l)
&gt;的此列表中出现的重复数据,绝大多数情况都是如此。 15和n
&lt; 100000,但是如果您需要保证,或len(l)
的较低值,只需使用set
来记录并跳过重复项(如果这是一个问题)(尽管正如您在评论中所观察到的那样,n
1}}接近len(l)!
,这将停止)。类似的东西:
def random_permutations(l, n):
pset = set()
while len(pset) < n:
random.shuffle(l)
pset.add(tuple(l))
return pset
然而,随着len(l)
变得越来越长,random.shuffle
变得越来越不可靠,因为列表的可能排列数量增加超过随机数生成器的周期!因此,l
的所有排列都不能以这种方式生成。此时,您不仅需要在一系列随机数上映射get_nth_permutation
,还需要一个随机数生成器,能够生成0
和len(l)
之间的每个随机数!分布相对均匀。这可能需要您找到更强大的随机源。
然而,一旦你有了,解决方案就像Mark Ransom的回答一样简单。
要了解random.shuffle
为len(l)
变得不可靠的原因,请考虑以下因素。 random.shuffle
只需在0
和len(l) - 1
之间选择随机数。但它根据内部状态选择这些数字,并且只需要有限(和固定)数量的状态。同样,您可以传递给它的可能种子值的数量是有限的。这意味着它可以生成的唯一数字序列集也是有限的;调用集合s
。对于len(l)! > len(s)
,永远不会生成某些排列,因为与这些排列相对应的序列不在s
中。
这会成为问题的确切长度是多少?我不确定。但是对于它的价值,由random
实施的mersenne twister的时期是2**19937-1。 shuffle docs以一般方式重申了我的观点;另请参阅维基百科对此事的评论here。
答案 1 :(得分:4)
而不是使用xrange
只需保持生成随机数,直到您拥有所需数量为止。使用set
可确保它们都是唯一的。
permutation_indices = set()
while len(permutation_indices) < k:
permutation_indices.add(random.randrange(total_permutations))
答案 2 :(得分:1)
我有一个nth_permutation的实现(不确定从哪里得到它)我为你的目的修改了。我相信这会足够快以满足您的需求
>>> def get_nth_permutation(population):
total_permutations = math.factorial(len(population))
while True:
temp_population = population[:]
n = random.randint(1,total_permutations)
size = len(temp_population)
def generate(s,n,population):
for x in range(s-1,-1,-1):
fact = math.factorial(x)
d = n/fact
n -= d * fact
yield temp_population[d]
temp_population.pop(d)
next_perm = generate(size,n,population)
yield [e for e in next_perm]
>>> nth_perm = get_nth_permutation(range(21))
>>> [next(nth_perm) for k in range(1,10)]
答案 3 :(得分:0)
您似乎正在搜索Knuth Shuffle!祝你好运!
答案 4 :(得分:0)
您可以使用itertools.islice
代替xrange()
:
CPython实现细节:xrange()旨在简单明了 快速实现可能会对此实施限制。 C Python的实现将所有参数限制为本机C long (“短”Python整数),还要求的数量 元素适合原生C长。如果需要更大的范围,a 备用版本可以使用itertools模块制作: islice(count(start,step),(stop-start + step-1 + 2 *(step&lt; 0))// step)。