随机数生成器,每次只返回一个数字

时间:2017-05-27 10:04:26

标签: python python-3.x random generator

Python是否有一个随机数生成器,每次调用next()函数时,它只返回一个随机整数?数字不应重复,并且生成器应在[1, 1 000 000]区间内返回唯一的随机整数。

我需要生成超过一百万个不同的数字,如果所有数字同时生成并存储在列表中,这听起来好像非常耗费内存。

7 个答案:

答案 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

然后,它只是选择acm的正确值,以保证LCG将生成一个完整的期间(这是唯一的保证你得到非重复的数字)。正如维基百科的文章所解释的那样,以下三个条件必须成立:

  1. mc需要相对素数。
  2. a - 1可被m
  3. 的所有素数因子整除 如果a - 1也可被4整除,则
  4. m可被4整除。
  5. 只需选择c的素数即可轻松保证第一个。此外,这是最后可以选择的值,这最终将允许我们稍微混合序列。

    a - 1m之间的关系更为复杂。在整个期间LCG中,m是期​​间的长度。或者换句话说,它是您的数字来自的数字范围。所以这就是你通常首先选择的。在您的情况下,您希望m1000000附近。选择确切的最大数字可能很困难,因为这会对您产生很大的限制(同时选择ac),因此您也可以选择大于此数字的数字,只需跳过你的范围以后。

    现在让我们选择m = 1000000m的主要因素是25。它也显然可以被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中获取非重复数字。当然,按照设计,这个序列将始终是相同的,因此当您选择这些数字时,它只是随机的一次。您可以切换ac的值,以获得不同的序列,只要您保持上述属性。

    这种方法的最大好处当然是您不需要存储以前生成的所有数字。它是一个恒定空间算法,因为它只需要记住初始配置和先前生成的值。

    当你进一步进入序列时,它也不会恶化。这是解决方案的一般问题,该解决方案只是继续生成随机数,直到找到之前未遇到过的新数据。这是因为生成的数字列表越长,使用均匀分布的随机算法命中不在该列表中的数字的可能性就越小。因此,获得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 的回答并没有让我满意,因为我需要有精确的边界,而其他包含列表的边界会导致内存堆积。

最初,我需要一个函数来生成从 ab 的数字,其中 a - b 可以是从 02^32 - 1 的任何值,这意味着这些数字的范围可能高达最大的 32 位无符号整数。

我自己的算法的想法很容易理解和实现。这是一个二叉树,下一个分支由 50/50 机会布尔生成器选择。基本上,我们将所有从 ab 的数字分成两个分支,然后决定从哪个分支产生下一个值,然后递归地这样做,直到我们最终得到单个节点,这些节点也被选中随机上升。

递归深度为:

\log+_{2}+(b - a)

,这意味着对于给定的堆栈限制 256,您的最高范围将是 2^256,这是令人印象深刻的。

注意事项:

  1. a 必须小于或等于 b - 否则不会显示任何输出。
  2. 包含边界,这意味着 unique_random_generator(0, 3) 将生成 [0, 1, 2, 3]

TL;DR - 这是代码

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