【问题标题】:How to generate unique(!) arrays/lists/sequences of uniformly distributed random如何生成唯一(!)均匀分布的随机数组/列表/序列
【发布时间】:2021-09-19 03:13:49
【问题描述】:

假设我生成一个包,即使用随机生成器生成 10 个随机数的一维数组。然后我生成另一个包含 10 个随机数的数组。我这样做X次。如何生成唯一的数组,即使经过一万亿代,也没有一个数组可以与另一个数组相等?

在一个数组中,元素可以重复。该数组只需与其他数组不同,其所有元素至少有一个不同的元素。

有什么 numpy 方法可以解决这个问题吗?是否有一些特殊的算法通过探索随机生成的一些空间来不同地工作?我不知道。

一个简单的答案是将数组写入文件并检查它们是否已经生成,但是对随后更大的文件进行 I/O 操作需要太多时间。

【问题讨论】:

  • 您想尝试一些策略,以某种方式确保“下一个”包以前从未见过,或者尝试将一个潜在的包与所有其他先前的包进行测试,看看是否有重复?

标签: python pandas numpy random


【解决方案1】:

Birthday problem 建议在某些时候您无需费心检查重复项。例如,如果 10 个元素的“包”中的每个值可以取超过 ~250 个值,那么在生成 1e12 个包后,您只有 50% 的机会看到重复项。每个元素可以采用的不同值越多,这种概率就越低。

您尚未在此问题中指定这些随机值是什么(除了均匀分布),但您的链接问题表明它们是 Python floats。因此,每个数字都有2**53 可以采用的不同值,因此看到重复的概率几乎为零。

有几种方法可以重新排列这个计算:

  1. 对于给定数量的状态和迭代次数,出现至少一次冲突的概率是多少
  2. 对于给定数量的状态,您可以生成多少次迭代以保持低于看到至少一次碰撞的给定概率
  3. 对于给定的迭代次数和发生冲突的概率,需要什么状态大小

下面的 Python 代码计算选项 3,因为它似乎最接近您的问题。 birthday attack 页面上提供了其他选项。

from math import log2, log1p

def birthday_state_size(size, p):
    # -log1p(p) is a numerically stable version of log(1/(1+p))
    return size**2 / (2*-log1p(-p))

log2(birthday_state_size(1e12, 1e-6)) # => ~100

因此,只要每个包中有 100 多个统一的状态位,一切都应该没问题。例如,两个或更多 Python 浮点数是可以的 (2 * 53),10 个整数具有 >= 1000 个不同的值 (10*log2(1000))。

您当然可以进一步降低概率,但正如 Wikipedia 文章中所述,低于 1e-15 会迅速接近计算机的可靠性。这就是为什么我说“几乎为零”,因为 10 个均匀分布的浮点数提供了 530 位状态。

【讨论】:

    【解决方案2】:

    重要提示:

    这将不允许您“任意”生成许多独特的包。请参阅@Prune 突出显示的限制。

    请注意,随着请求包的数量接近独特包的数量,查找包的时间会越来越长。我还设置了一个安全装置,这样在尝试了一定次数后它就会放弃。

    随意调整:

    import random
    
    ## -----------------------
    ## Build a unique pack generator
    ## -----------------------
    def build_pack_generator(pack_length, min_value, max_value, max_attempts):
        existing_packs = set()
    
        def _generator():
            pack = tuple(random.randint(min_value, max_value) for _ in range(1, pack_length +1))
            pack_hash = hash(pack)
    
            attempts = 1
            while pack_hash in existing_packs:
                if attempts >= max_attempts:
                    raise KeyError("Unable to fine a valid pack")
                pack = tuple(random.randint(min_value, max_value) for _ in range(1, pack_length +1))
                pack_hash = hash(pack)
                attempts += 1
    
            existing_packs.add(pack_hash)
            return list(pack)
    
        return _generator
    
    generate_unique_pack = build_pack_generator(2, 1, 9, 1000)
    ## -----------------------
    
    for _ in range(50):
        print(generate_unique_pack())
    

    【讨论】:

    • 是的,给定足够的 RAM 来保存 existing_packs,这是我想到的实现:set 在非平凡键上使用哈希。
    【解决方案3】:

    假设一包中的 10 个数字均在 [0..max] 范围内。然后可以将每个包视为以 max+1 为基数的 10 位数字。显然,max 的大小决定了有多少独特的包。例如,如果 max=9,则从 [0000000000] 到 [9999999999] 有 10,000,000,000 个可能的唯一包。

    然后问题归结为在正确范围内生成唯一数字。

    鉴于您的“万亿”,那么在该范围内生成有保证的唯一数字的最佳方法可能是使用具有正确大小输出的加密。除非您想要 64 位 (DES) 或 128 位 (AES) 输出,否则您将需要某种格式保留加密来获得所需范围内的输出。

    对于输入,只需依次加密数字 0、1、2、...。加密保证在给定相同密钥的情况下,每个唯一输入的输出都是唯一的。您只需要跟踪输入数字的进展情况。鉴于此,您可以在 max 施加的限制内根据需要生成更多独特的包。在那之后,输出将开始重复。

    显然,作为最后一步,您需要将加密输出转换为 10 位基数 max+1 数字并将其放入数组中。

    【讨论】:

    • 感谢您的回复!我不太喜欢加密,但我尝试查找它。
    • 不是为了安全,而是为了唯一性。它省去了记录到目前为止您生成的随机数的麻烦。有关更多详细信息,请参阅Format Preserving Encryption。对于您的目的,骑自行车应该就足够了。
    【解决方案4】:

    这是一个困难的要求,因为 RNG 的属性之一是它应该随机重复序列。

    您还遇到了尝试记录数 TB 先前结果的问题。一旦您可以尝试的是形成现有数组的哈希表(用于搜索速度)。使用它在很大程度上取决于您是否有足够的 RAM 来保存整个列表。

    如果不是,您可能会考虑磁盘映射某种快速搜索结构。例如,您可以实现一个哈希键的磁盘二叉树,每当您将树的大小加倍(带有插入)时重新平衡。这使您可以保持文件打开并通过seek 查找条目,而无需在内存中表示整个文件。

    您还可以维护该表的内存索引,使用该索引将您的 seek 驱动到正确的文件部分,然后仅读取文件的一小部分以进行最终搜索。

    这是否有助于集中您的实施?

    【讨论】:

    • 是的,它确实有帮助,谢谢。我有 TB 的 RAM,并且我对模拟的其他限制,它不会是限制因素,我会在之前达到我的组合计数。我必须查看所有内容,务实地尝试一下。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 2013-12-09
    • 1970-01-01
    • 2013-07-22
    相关资源
    最近更新 更多