Python是否有一个随机数生成器,每次调用next()
函数时,它只返回一个随机整数?数字不应重复,并且生成器应在[1, 1 000 000]
区间内返回唯一的随机整数。
我需要生成超过一百万个不同的数字,如果所有数字同时生成并存储在列表中,这听起来好像非常耗费内存。
答案 0 :(得分:6)
您正在寻找一个完整时期的linear congruential generator。这将允许您在目标数字范围内获得伪随机序列的非重复数字。
实施LCG实际上非常简单,看起来像这样:
def lcg(a, c, m, seed = None):
num = seed or 0
while True:
num = (a * num + c) % m
yield num
然后,它只是选择a
,c
和m
的正确值,以保证LCG将生成一个完整的期间(这是唯一的保证你得到非重复的数字)。正如维基百科的文章所解释的那样,以下三个条件必须成立:
m
和c
需要相对素数。a - 1
可被m
a - 1
也可被4整除,则m
可被4整除。只需选择c
的素数即可轻松保证第一个。此外,这是最后可以选择的值,这最终将允许我们稍微混合序列。
a - 1
和m
之间的关系更为复杂。在整个期间LCG中,m
是期间的长度。或者换句话说,它是您的数字来自的数字范围。所以这就是你通常首先选择的。在您的情况下,您希望m
在1000000
附近。选择确切的最大数字可能很困难,因为这会对您产生很大的限制(同时选择a
和c
),因此您也可以选择大于此数字的数字,只需跳过你的范围以后。
现在让我们选择m = 1000000
。 m
的主要因素是2
和5
。它也显然可以被4
整除。因此,对于a - 1
,我们需要一个2 * 2 * 5
倍数的数字来满足条件2和3.让我们选择a - 1 = 160
,所以a = 161
。
对于c
,我们使用的是一个位于我们范围之间的随机素数:c = 506903
将它放入我们的LCG中可以得到我们想要的序列。我们可以从范围(0 <= seed <= m
)中选择任何种子值作为序列的起点。
所以让我们试一试,验证我们认为实际上有效的方法。为此,我们只是从集合中收集生成器中的所有数字,直到我们重复一次。那时,我们应该在集合中有m = 1000000
个数字:
>>> g = lcg(161, 506903, 1000000)
>>> numbers = set()
>>> for n in g:
if n in numbers:
raise Exception('Number {} already encountered before!'.format(n))
numbers.add(n)
Traceback (most recent call last):
File "<pyshell#5>", line 3, in <module>
raise Exception('Number {} already encountered before!'.format(n))
Exception: Number 506903 already encountered before!
>>> len(numbers)
1000000
这是正确的!因此,我们创建了一个伪随机数字序列,允许我们从我们的范围m
中获取非重复数字。当然,按照设计,这个序列将始终是相同的,因此当您选择这些数字时,它只是随机的一次。您可以切换a
和c
的值,以获得不同的序列,只要您保持上述属性。
这种方法的最大好处当然是您不需要存储以前生成的所有数字。它是一个恒定空间算法,因为它只需要记住初始配置和先前生成的值。
当你进一步进入序列时,它也不会恶化。这是解决方案的一般问题,该解决方案只是继续生成随机数,直到找到之前未遇到过的新数据。这是因为生成的数字列表越长,使用均匀分布的随机算法命中不在该列表中的数字的可能性就越小。因此,获得1000000个数字可能会花费很长时间来生成基于内存的随机生成器。
但是,当然,使用这种简单的算法只执行一些乘法和一些加法并不是非常随机的。但是你必须记住,这实际上是大多数伪随机数发生器的基础。所以random.random()
在内部使用这样的东西。只是m
更大,所以你没有注意到它。
答案 1 :(得分:2)
如果您真的关心内存,可以使用NumPy
数组(或Python array
)。
一百万个int32
的NumPy数组(足以包含0到1 000 000之间的整数)只消耗〜4MB,Python本身需要~36MB(每个整数大约28byte,每个大小8字节) list element + overallocation)用于相同的列表:
>>> # NumPy array
>>> import numpy as np
>>> np.arange(1000000, dtype=np.int32).nbytes
4 000 000
>>> # Python list
>>> import sys
>>> import random
>>> l = list(range(1000000))
>>> random.shuffle(l)
>>> size = sys.getsizeof(l) # size of the list
>>> size += sum(sys.getsizeof(item) for item in l) # size of the list elements
>>> size
37 000 108
你只需要唯一的值,并且你有一个连续的范围(100万个请求的项目和100万个不同的数字),所以你可以简单地改变范围,然后从你的混洗阵列中产生项目:
def generate_random_integer():
arr = np.arange(1000000, dtype=np.int32)
np.random.shuffle(arr)
yield from arr
# yield from is equivalent to:
# for item in arr:
# yield item
可以使用next
调用它:
>>> gen = generate_random_integer()
>>> next(gen)
443727
然而,这会丢掉使用NumPy的性能优势,因此如果您想使用NumPy,请不要使用生成器,只需在阵列上执行操作(矢量化 - 如果可能)。它消耗的内存比Python少得多,而且速度可以快几个数量级(速度快10-100倍并不罕见!)。
答案 2 :(得分:1)
对于大量非重复随机数,请使用加密。使用给定密钥,加密数字:0,1,2,3 ......由于加密是唯一可逆的,因此只要您使用相同的密钥,每个加密的数字都保证是唯一的。对于64位数字,请使用DES。对于128位数字,请使用AES。对于其他大小的数字,请使用一些格式保留加密。对于纯数字,您可能会发现Hasty Pudding密码很有用,因为它允许大范围的不同位大小和非位大小,如[0..5999999]。
跟踪您加密的密钥和最后一个号码。当您需要一个新的唯一随机数时,只需加密到目前为止尚未使用的下一个数字。
答案 3 :(得分:1)
考虑到你的数字应该适合64位整数,如果你的处理计算机可以承受最简单的方法是使用shuffle,那么存储在列表中的一百万个将达到64兆字节加上列表对象开销。 / p>
import random
randInts = list(range(1000000))
random.shuffle(randInts)
print(randInts)
请注意,另一种方法是跟踪以前生成的数字,这样就可以让你存储所有数字。
答案 4 :(得分:1)
我只是需要那个功能,令我惊讶的是,我没有找到适合我需要的任何东西。 @poke 的回答并没有让我满意,因为我需要有精确的边界,而其他包含列表的边界会导致内存堆积。
最初,我需要一个函数来生成从 a
到 b
的数字,其中 a - b
可以是从 0
到 2^32 - 1
的任何值,这意味着这些数字的范围可能高达最大的 32 位无符号整数。
我自己的算法的想法很容易理解和实现。这是一个二叉树,下一个分支由 50/50 机会布尔生成器选择。基本上,我们将所有从 a
到 b
的数字分成两个分支,然后决定从哪个分支产生下一个值,然后递归地这样做,直到我们最终得到单个节点,这些节点也被选中随机上升。
递归深度为:
,这意味着对于给定的堆栈限制 256,您的最高范围将是 2^256,这是令人印象深刻的。
注意事项:
a
必须小于或等于 b
- 否则不会显示任何输出。unique_random_generator(0, 3)
将生成 [0, 1, 2, 3]
。import math, random
# a, b - inclusive
def unique_random_generator(a, b):
# corner case on wrong input
if a > b:
return
# end node of the tree
if a == b:
yield a
return
# middle point of tree division
c = math.floor((a + b) / 2)
generator_left = unique_random_generator(a, c) # left branch - contains all the numbers between 'a' and 'c'
generator_right = unique_random_generator(c + 1, b) # right branch - contains all the numbers between 'c + 1' and 'b'
has_values = True
while (has_values):
# decide whether we pick up a value from the left branch, or the right
decision = bool(random.getrandbits(1))
if decision:
next_left = next(generator_left, None)
# if left branch is empty, check the right one
if next_left == None:
next_right = next(generator_right, None)
# if both empty, current recursion's dessicated
if next_right == None:
has_values = False
else:
yield next_right
else:
yield next_left
next_right = next(generator_right, None)
if next_right != None:
yield next_right
else:
next_right = next(generator_right, None)
# if right branch is empty, check the left one
if next_right == None:
next_left = next(generator_left, None)
# if both empty, current recursion's dessicated
if next_left == None:
has_values = False
else:
yield next_left
else:
yield next_right
next_left = next(generator_left, None)
if next_left != None:
yield next_left
用法:
for i in unique_random_generator(0, 2**32):
print(i)
答案 5 :(得分:0)
import random
# number of random entries
x = 1000
# The set of all values
y = {}
while (x > 0) :
a = random.randint(0 , 10**10)
if a not in y :
a -= 1
这样您就可以确定您拥有完全随机的唯一值
x
表示您想要的值数
答案 6 :(得分:-4)
您可以轻松自己制作一个:
from random import random
def randgen():
while True:
yield random()
ran = randgen()
next(ran)
next(ran)
...