从巨大范围中选择不重复的随机元素

0 投票
2 回答
76 浏览
提问于 2025-04-14 15:34

我有一个关于米勒-拉宾素性测试的实现:

def miller_rabin(n, rounds = 40):
    if n == 1:
        return False
    if n in [2, 3]:
        return True
    if n % 2 == 0:
        return False
    d = n - 1
    s = 1
    while d % 2 == 0:
        d = d // 2
        s += 1
    chosen = []
    for _ in range(rounds):
        a = random.randrange(2, n-1)
        x = gmpy2.powmod(a, d, n)
        for _ in range(s):
            y = gmpy2.powmod(x, 2, n)
            if y == 1 and x != 1 and x != n-1:
                return False
            x = y
        if y != 1:
            return False
    return True

默认情况下,它会进行40轮测试。每次测试时,它会从2到n-1之间随机选择一个数字。

现在我想知道有没有什么快速的方法可以避免重复选择相同的数字。

如果我这样做:

values = random.sample(range(2, n-1), rounds)

我就会遇到错误:Python的整数太大,无法转换为C语言的ssize_t类型。

2 个回答

0

这个错误的完整追踪信息显示,问题出在随机模块试图获取输入范围的长度。

看起来在获取范围对象长度的过程中,它试图把Python的大整数转换成64位的C语言整数,然后再转换回Python的大整数。别问我为什么要做这种奇怪的转换——其实它完全可以一直使用Python的大整数,这样就能避免这个问题。

有个评论建议使用random.shuffle(),但它也会用到len,所以很可能也会遇到同样的问题。

我不太确定怎么避免这个问题,除非完全重新实现random.sample()

1

处理这个问题时,一个重要的因素是 n 的大小。如果 n 小于 2**63,你可以直接使用

random.sample(range(n), rounds)

来获取一个包含所需大小的唯一值的列表。

如果 n 的值比较大,像40这样的小样本数量碰撞的概率几乎为零。不过,如果你想确保绝对安全,最简单的办法就是使用一种拒绝技术。这种方法在Python中使用 set() 非常简单,因为它可以保证元素的唯一性。下面的代码演示了这一点:

import random

def unique_rand_set(n, lower_bd = 2, qty = 40):
    if n < 2**63:
        return random.sample(range(lower_bd, n-1), qty)
    s = set()
    while len(s) < qty:
        s.add(random.randrange(lower_bd, n))
    return s

# The following code just demonstrates usage of the function
n = 1 << 100
my_set = unique_rand_set(n, 5)
for value in my_set:
    print(value)

根据上面的 unique_rand_set() 函数,你需要将

for _ in range(rounds):
    a = random.randrange(2, n-1)
    ...

改成

my_set = unique_rand_set(n)
for a in my_set:
    ...

请注意,由于列表和集合都是可迭代的,所以 for a in my_set: 这个写法无论函数返回哪种类型都能正常工作。

我们从生日问题中知道,直到生成的值接近 O(sqrt(rounds)) 时,碰撞的概率才会显著增加,因此你可以生成数百万个值,而碰撞的可能性非常小。

撰写回答