【问题标题】:How to randomly shuffle a list that has more permutations than the PRNG's period?如何随机打乱排列比 PRNG 周期多的列表?
【发布时间】:2016-03-12 09:23:49
【问题描述】:

我有一个包含大约 3900 个元素的列表,我需要随机排列这些元素以生成统计分布。我环顾四周,发现这个Maximal Length of List to Shuffle with Python random.shuffle 解释了Python 中PRNG 的周期是2**19937-1,这导致了一个最大长度为2080 的列表,然后就不可能生成所有可能的排列。我只产生列表的 300-1000 个排列,因此我不太可能产生重复的排列,但是,因为这产生了一个统计分布,我希望将所有可能的排列作为潜在样本。

【问题讨论】:

  • 您想生成 3900 个元素的所有可能排列?
  • 你考虑过使用os.urandom吗? urandom 适合加密,所以我认为它应该适用于你正在做的任何事情。
  • 出于统计目的——事实上,基本上任何目的——对可能排列的限制是没有问题的。如果您担心这一点,那么您需要担心的与理想分布的偏差要大得多。
  • 作为一个需要担心的“更大的偏差”的例子,in Python 2, random.sample-ing xrange(1, sys.maxsize) always returns a number congruent to 1 mod 2**10. 另外,对于建议加密 PRNG 的人,即使是 那些 也没有时间产生所有可能的排列,甚至密码学家仍然不在乎。
  • @m69 不,它只会给你一组不同的 2^19937。如果您第二次使用不同的 PRNG 置换,最好添加它们的句点(如果它们之间没有交互)。

标签: python algorithm random


【解决方案1】:

我同意@user2357112 的观点,即这不太可能是真正的问题——但您似乎应该能够以至少所有排列都是可能的方式使用标准的random 模块。

您可以采取分而治之的方法。使用初始种子将列表划分为 2 个列表,每个列表大约 2000 个。此类分区的数量大致为C(4000,2000),大约为1.66 x 10^1202。这比周期少,这表明至少有可能使用random.sample() 生成所有此类分区。然后 - 重新播种随机数生成器并置换前半部分。然后 - 第二次重新播种并置换下半场。也许在重新播种之前稍微延迟一点,这样您就不会遇到涉及系统时钟分辨率的问题。您还可以尝试将初始列表随机划分为大量较小的列表。

从数学上很容易看出,如果您将一个列表随机划分为多个子列表,以便每个分区具有相同的可能性,然后您以这样一种方式排列每个子列表,使所有子列表排列的可能性相同,并将这些子列表排列粘合在一起要获得一个整体列表排列,那么所有整体列表排列的可能性都相同。

这是一个实现:

import random, time

def permuted(items, pieces = 2):
    sublists = [[] for i in range(pieces)]
    for x in items:
        sublists[random.randint(0,pieces-1)].append(x)
    permutedList = []
    for i in range(pieces):
        time.sleep(0.01)
        random.seed()
        random.shuffle(sublists[i])
        permutedList.extend(sublists[i])
    return permutedList

我不确定time.sleep(0.01) 是否真的需要。我担心的是,如果重新播种在一毫秒内发生,那么在某些系统上可能会使用相同的种子。

作为最终的评论,因为上面的功能(具有适当的选择pieces),无法通过简单的计数参数错过某些排列(比较初始状态数量的排列数)这个本身并不能证明所有排列实际上都是可能的。这将需要对随机数生成器、为其提供种子的散列函数和洗牌算法进行更详细的分析。

【讨论】:

  • 这似乎是一个很好的解决方案。你能提供一个随机划分原始列表的代码示例吗?
【解决方案2】:

有比 MT 更长周期的 PRNG,但它们很难找到。

获得全部3090!组合,您需要 40,905 位熵。这大约是 5kb。您应该能够从诸如 random.org 之类的某个地方多次毫无问题地获取一大块大小的字节。为了获得精确的平衡,您必须添加一些并进行拒绝采样。即,一次抓取 12 位(0..4095),并拒绝高于当前循环索引的数字。这可能会增加所需的位数,但可能不会超过 8kb。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2019-12-16
    • 2016-06-25
    • 1970-01-01
    相关资源
    最近更新 更多