列表的N个不同排列的随机样本

时间:2019-01-05 08:29:55

标签: python python-3.x random permutation discrete-mathematics

假设我有一个任意长度k的Python列表。现在,假设我想要一个n的随机样本,(其中n <= k!)该列表的 distinct 排列。我很想尝试:

import random
import itertools

k = 6
n = 10

mylist = list(range(0, k))

j = random.sample(list(itertools.permutations(mylist)), n)

for i in j:
  print(i)

但是,自然地,当k太大时,此代码将变得非常慢。鉴于我可能正在寻找的n的排列数与排列总数相比相对较小,因此不需要计算所有排列。但重要的是,最终列表中的所有排列都不能重复。

您将如何更有效地实现这一目标?请记住,mylist可以是任何东西的列表,为简单起见,我只是使用list(range(0, k))

2 个答案:

答案 0 :(得分:2)

您可以生成排列,并跟踪已经生成的排列。为了使其更加通用,我制作了一个生成器函数:

import random

k = 6
n = 10

mylist = list(range(0, k))

def perm_generator(seq):
    seen = set()
    length = len(seq)
    while True:
        perm = tuple(random.sample(seq, length))
        if perm not in seen:
            seen.add(perm)
            yield perm

rand_perms = perm_generator(mylist)

j = [next(rand_perms) for _ in range(n)]

for i in j:
    print(i)

答案 1 :(得分:1)

天真的实现

下面是我做过的天真的实现(通过@ Tomothy32很好地实现,使用生成器的纯PSL):

import numpy as np

mylist = np.array(mylist)
perms = set()
for i in range(n):                          # (1) Draw N samples from permutations Universe U (#U = k!)
    while True:                             # (2) Endless loop
        perm = np.random.permutation(k)     # (3) Generate a random permutation form U
        key = tuple(perm)
        if key not in perms:                # (4) Check if permutation already has been drawn (hash table)
            perms.update(key)               # (5) Insert into set
            break                           # (6) Break the endless loop
    print(i, mylist[perm])

它依靠numpy.random.permutation随机置换序列。

关键思想是:

  • 生成新的随机排列(索引随机排列);
  • 检查排列是否已存在并将其存储(如tuple的{​​{1}},因为它必须散列)以防止重复;
  • 然后使用索引置换来置换原始列表。

这个朴素的版本不会直接遭受itertools.permutations函数的阶乘复杂度int的影响,该函数会在从中采样之前生成所有O(k!)排列。

关于复杂性

关于算法设计和复杂性,有一些有趣的事情...

如果我们要确保循环可以结束,则必须强制执行k!,但不能保证。此外,评估复杂度还需要知道在发现新的随机元组并将其破坏之前,无限循环实际上将循环多少次。

限制

让我们封装@ Tomothy32编写的函数:

N <= k!

例如,此调用仅适用于很小的import math def get_perms(seq, N=10): rand_perms = perm_generator(mylist) return [next(rand_perms) for _ in range(N)]

k<7

但是当get_perms(list(range(k)), math.factorial(k)) 增长时,将在O(k!)复杂性(时间和内存)之前失败,因为找到所有其他k密钥后,它会归结为随机地找到唯一的丢失密钥。 / p>

总是看起来光明的一面...

另一方面,当k!-1时,该方法似乎可以在合理的时间内生成合理数量的置换元组。例如,可以在不到一秒钟的时间内绘制出多个N<<<k!个长度为N=5000的元组,其中k

enter image description here enter image description here

10 < k < 1000k保持较小且N时,该算法似乎很复杂:

  • 恒定与N<<<k!
  • 线性与k

这有点有价值。