【问题标题】:Random Sample of N Distinct Permutations of a List列表的 N 个不同排列的随机样本
【发布时间】:2019-05-31 16:44:10
【问题描述】:

假设我有一个任意长度的 Python 列表k。现在,假设我想要一个随机样本 n ,(其中 n 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))。

【问题讨论】:

  • 这个问题简化为能够生成第 n 个字典排列。一旦能够做到这一点,您就可以创建从序列1 to n(其中 n 是排列的总数)到所有排列的映射。现在我们可以轻松地生成数字1 to n 的随机样本S,然后为i 中的所有i 单独生成ith 排列i>S.

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


【解决方案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。

关键思想是:

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

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

关于复杂性

算法设计和复杂性有一些有趣的地方......

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

限制

我们来封装@Tomothy32写的函数:

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

但在k 增长时会在O(k!) 复杂性(时间和内存)之前失败,因为它归结为在找到所有其他k!-1 键时随机找到一个唯一的缺失键。

永远往好的方向看...

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

当k 和N 保持较小且N<<<k! 时,算法似乎具有复杂性:

  • 常数与k;
  • 线性与N。

这在某种程度上很有价值。

【讨论】:

  • IIRC,集合成员资格测试是O(1)。
  • @Tomothy32 你的意思是平均情况,但是有哈希表,所以最坏的情况是O(n),例如:单个槽被所有值填充,不太可能但渐近。
  • 确实如此,但是再次发生这种情况的概率基本上为零。我只是说我不认为这是这种情况下的瓶颈,生成排列的成本可能更高(我相信它是O(N))。
  • @Tomothy32 我已经更新了我的答案,调查了复杂性。你介意复习一下吗。谢谢分享。
【解决方案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)

【讨论】:

  • 看来我们的想法是一样的。你的版本很好,它只依赖于 PSL 并使用生成器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多