Python 十种生成随机数的方法以及它们的优缺点 Top 10 Ways to Generate Random Numbers Using Python
作者:禅与计算机程序设计艺术
1.简介
在编程语言中生成随机数一直是一件很重要的事情,随着越来越多的计算机硬件性能的提升,如何高效地生成随机数也成为一个重要的话题。在本文中,我将会分享十种生成随机数的方法以及它们的优缺点。我并不会详细介绍每一种方法,而只是简单的讲解一下它的原理和用途。希望能给大家带来一些启发。
2.概览
随机数生成方法
随机数(Random number)是指由物理或者数学过程生成的,具有统计规律性的数字序列。它是通过各种方法从一定的初始状态出发,经过一定的计算过程产生出来的。
1.伪随机数生成器 (Pseudo-random number generator PNR)
伪随机数生成器(PNR),又称确定随机数生成器、算法icrgen或系统randomic,是一种用来生成疑似随机数序列的数学模型,包括真随机数生成器等不同类型,但其基本思想和算法却十分相似。其基本原理就是利用一定算法、计时器和种子(Seed)值作为输入参数,产生一个连续的、均匀分布的数列。该数列即为伪随机数序列。这种方法可以对任意输入(包括种子值)产生相同的输出序列,同时也具备不可预测性、随机性和周期性。
2.基于传统型随机数生成器的随机数生成算法
(1)线性congruential generator (LCG)
LCG是一个古典的随机数生成算法,由谢尔顿·威尔逊()于1951年提出,他的“线性同余”这个词汇源自其论文中的讨论。1951年的论文“The Art of Computer Programming, Volume 2”中首次描述了这个算法,并获得了当时计算机界最高声誉。
线性congruential generator的基本思路是通过指定一组公共参数,求出一个线性方程,使得产生下一个随机数取决于当前随机数的值,最后生成一串符合统计规律的伪随机数。这些公共参数决定了随机数序列的质量,因此不能轻易泄露。
# python实现LCG算法
def lcg_random():m = 2**31 - 1 #modulusa = 1664525 #multiplierc = 1013904223 #incrementorseed = int(time()) #seed valuewhile True:seed = (a * seed + c) % m #update the seedyield seed / m #generate random numbers and output them
如上所示,LCG算法需要三个参数——m、a、c。其中,m表示模数,a和c分别表示乘数和增量。算法初始值为s0,通过计算得到下一个随机数Xn+1。Xn+1=(axn+c)mod m;Xn=Xn−1。除法mod m是为了防止结果溢出到下个整数,避免出现整点。由于Xn可以直接作为随机数输出,因此不需要其他变量。LCG算法产生的随机数的周期性较好,但是周期过长容易导致生成出重复值。
(2)Mersenne Twister随机数生成算法
Mersenne Twister是一种比较新的随机数生成算法,由Matsumoto和Nishimura于1997年提出的。它的优点是比传统的LCG算法更好的性能,且更易于理解。
其基本思路是在LCG基础上加以改进,首先确定三个参数:w、n、r。其中,w表示素数个数,n表示输出的比特位数,r为额外位数。n比w小时,只能生成无限多个不同的随机数,等于n时才可以保证周期性。r用于保证随机数序列是平稳的,但实际上没有直接应用。算法初始值为s0,通过计算得到下一个随机数Xn+1。Xn+1=(Xn(2k) mod n)mod w;Xn=Xn−r。算法每运行一次,便会循环n-r次。对于生成n比特的随机数,只需取Wn mod 2^(n/w)。另外,当输入数字序列到达一定长度时,重新初始化参数,可以保证最终序列不再重复。
# python实现Mersenne Twister算法
import time
MT = [0]*624
MT[0] = int(time()%2**32) #seed value
index = 0 #index for MT array
lower_mask = (1 << 31)-1 #bit mask for lowest w bits
upper_mask = ((1 << 31)-1)^((1 << 31)-1 >> n) #bit mask for upper r bits
for i in range(1, n):MT[i] = (1812433253*(MT[i-1]^(MT[i-1] >> 30))+i)&lower_mask
for i in range(n, 624):MT[i] = (MT[i-n]^(MT[i-n]>>30))*1812433253&lower_mask
def mt_random(): #yielding values from MT sequenceglobal indexif index == 0: #reload MT if neededself._reload_mt()y = MT[index] #get current valuey ^= ((y >> u)>>1) & d #temper with highest w bitsy ^= ((y << s) & b) ^ (y << t) #temper with middle n bits y ^= ((y << u) & c) | (y >> v) #temper with lowest w bits index += 1 #move to next position in MTreturn abs(float(int(((y>>1)+1)*m)))/(2*m-1) #return random float between 0 and 1
def _reload_mt(self): #reloading MT when necessaryglobal MT, indexf = open('/dev/urandom', 'rb') #open urandom devicedata = bytearray(f.read(4)) #read 4 bytesf.close() #close filefor i in range(0, n): #convert bytes to integerval = int(data[i/4])+(val<<8)+(val<<16)+(val<<24) if i%4==0 else val|(int(data[i/4])<<(8*((3-i)%4)))MT[i] = (MT[i] ^ ((MT[i-1] ^ (MT[i-1] >> 30))*1664525)) + val
如上所示,Mersenne Twister算法与LCG算法有着许多相似之处,也有不同的地方。首先,他们都是可以生成可重复随机数序列的算法,并且都有自己的周期性。然而,Mersenne Twister算法比LCG算法生成的随机数要精确一些,而且可以追踪每个值的中间结果。另外,Mersenne Twister算法比LCG算法更易于理解,需要的计算量更少。
(3)AES加密算法生成随机数
AES加密算法是一种常用的对称加密算法,其安全性得益于设计良好的轮密钥分组密码体制。因此,它可以用于生成伪随机数序列。
如下图所示,AES加密算法是一种分组加密算法,分为两个阶段:初始轮密钥扩展阶段和加密轮密钥扩展阶段。初始轮密钥扩展阶段将原始密钥扩展成一系列的轮密钥。加密轮密钥扩展阶段根据初始轮密钥进行加密运算,以生成密文。
使用AES加密算法生成随机数,主要有两种方式:
(1)密码学上的随机数生成
通过调整初始轮密钥的数目、轮数、偏移量、偏移向量等参数,可以控制生成的随机数的特性,例如,是否具有统计规律性,数值范围等。但是,这种方式通常难以实现,而且周期性也不是很长。
(2)系统随机数生成
在系统层面,可以通过调用系统函数(如GetTickCount())获取当前时间,然后对其进行处理,生成随机数序列。虽然可以控制随机数的特性,但是仍然存在周期性问题,而且生成的随机数可能与其它用途的随机数发生冲突。
3.其他类型的随机数生成算法
(1)网络流量模式生成随机数
网络流量模式也可以生成随机数。一种流行的网络流量模式叫做TCP通信模式,它定义了一个交换机接收到的数据包之间的平均间隔时间,以及网络传输速度。在这种模式下,随机数生成器就可以根据时间信息,确定每个数据包发送的时间。
# python实现TCP通信模式生成随机数
PACKET_INTERVALS = {0: 1.0, #interval zero is special case (don't wait at all before sending packet)1: 1.0, #one interval = one second (fixed delay before first packet)2: 1.0, #two intervals = two seconds (fixed delay before each subsequent packet)...
}
MIN_PACKET_INTERVAL = min(PACKET_INTERVALS.values())
MAX_PACKET_INTERVAL = max(PACKET_INTERVALS.values())
class TcpPacketGenerator(object):def __init__(self, interval_type='uniform'):self.intervals = {}self.packet_count = 0self.prev_timestamp = Noneself.interval_type = interval_typedef get_next_packet(self):now = datetime.datetime.now()if not self.prev_timestamp:self.prev_timestamp = nowelapsed_time = 0.0else:elapsed_time = (now - self.prev_timestamp).total_seconds()interarrival_time = 0.0if self.interval_type == 'uniform':interarrival_time = random.uniform(MIN_PACKET_INTERVAL, MAX_PACKET_INTERVAL)elif self.interval_type == 'poisson':lamda = sum([1/x for x in PACKET_INTERVALS.values()])interarrival_time = random.expovariate(lamda)else:raise ValueError("Invalid interval type")sleep_time = interarrival_time - elapsed_timeif sleep_time > 0.0:time.sleep(sleep_time)timestamp = str(int(now.timestamp()))payload = "Packet #%d" % self.packet_countself.prev_timestamp = nowself.packet_count += 1self.intervals[timestamp] = interarrival_timereturn {'timestamp': timestamp, 'payload': payload}
TCP通信模式生成随机数的基本思路是,首先确定一个最小的、最大的、平均的、或任意的间隔时间,并设置相应的模式。然后,生成器等待相应的时间间隔,再发送下一个数据包。数据的发送时间和接收时间记录在字典中,以便之后统计和分析。
(2)加权随机数生成算法
加权随机数生成算法(Weighted random number generation algorithm,WRNG),也称作等概率随机数生成算法。它是利用概率分布(Probability distribution)来选择随机事件。最常用的分布形式为离散型的几何分布,即设定各个事件发生的次数。每一次事件发生后,相应的权重减一,直至全部权重都变为零。在此之后,随机事件按照其对应的概率分布被选中,依概率递增的方式产生随机数。
基于这种思想,加权随机数生成算法需要一个权重序列,并根据该权重序列生成随机数。通常情况下,权重序列需要满足概率质量函数(PMF)。具体而言,假设事件{Ai}={1,…,n},对应概率Pm(i),则Pm(i)>=0且P∑iPm(i)=1。则对任何0<=j<=n,有Pj=Pm(j)/P∑iPm(i)。则权重序列为{Pj|0<=j<=n}。当所有事件发生频率相同时,即Pm(i)=1/n时,权重序列就是等概率分布。否则,称之为近似等概率分布。
有两种常用的等概率随机数生成算法,即离散傅里叶变换法和蒙特卡洛模拟法。
1.离散傅里叶变换法
离散傅里叶变换法(DFT)是一种快速生成随机数的算法,但其输出只能属于实数空间。DFT的基本思路是,设定一个信号(如计时器)的频率,通过某些变换,将信号转换为频谱(Spectrum),再通过逆变换,将频谱恢复为随机数序列。
DFT算法要求信号的频率分布必须满足正弦分布,即f(k)sin(2πfkt)≈σ^2(k-k0), k∈[−N/2, N/2], k0=0.5N,σ^2为带宽,t为时间。由于频率分布不一致,输出的随机数也不会一致。为消除这种不一致性,可以使用加性噪声,即添加服从高斯分布的白噪声,从而使得输出随机数序列变得“真随机”。
# python实现离散傅里叶变换法生成随机数
import numpy as np
import scipy.fft as fft
import math
N = 256 #number of samples per period
T = 1.0/N #period length
BW = N/2 #signal bandwidth (as fraction of sample rate)
FSR = N/T #frequency resolution (in Hz)
#create Gaussian noise process
sig = np.zeros(N)
for i in range(N//2):freq = (i+1)/(N/2)*BWampl = pow(math.e,-freq**2/(2*0.25))phase = random.uniform(-np.pi, np.pi)sig[i] = ampl*math.cos(phase)sig[-i-1] = sig[i]
noisy_sig = sig + np.random.normal(scale=0.2, size=len(sig))
#apply Fourier transform
spec = np.abs(fft.fft(noisy_sig))
freqs = np.arange(N//2+1)*(FSR/N)
#select frequencies above cutoff frequency
fcutoff = 1.0/(2*T) #cutoff frequency (in Hz)
indices = [(round(x/fcutoff*N)//2)*2 for x in range(N)] #indices of selected frequencies
random_vals = []
for j in indices:zeta = complex(spec[j].real, spec[j].imag)phi = random.uniform(0, 2*np.pi)rescaled_zeta = (B**(-N/2))*zeta*exp(1j*phi*N)random_vals.append(abs(rescaled_zeta)**2)
#normalize random values to ensure mean=1.0 and stdev=sigma^2/sqrt(2)
avg = sum(random_vals)/len(random_vals)
var = sum([(x-avg)**2 for x in random_vals])/len(random_vals)
stddev = math.sqrt(var)
scaled_random_vals = [(x-avg)/(stddev*math.sqrt(2)) for x in random_vals]
print(scaled_random_vals[:5]) #show first five random values
如上所述,离散傅里叶变换法生成随机数的基本思路是,先创建一个平坦的基波信号,并加入高斯噪声,然后进行离散傅里叶变换,将信号转换为频谱。选择频率大于截止频率的部分,并把它们映射到随机值域。为了保证输出随机数的均值为1.0,标准差为σ^2/sqrt(2),需要对每个随机数除以 sqrt(2)。
2.蒙特卡洛模拟法
蒙特卡洛模拟法(Monte Carlo simulation,MCS)是一种经典的随机数生成算法,其思路是利用概率分布来估算或预测某种随机变量。它模拟一个事件集合,假设每一种事件发生的概率不同,然后以概率平均的方式去计算结果。由于每次模拟都是一个独立事件,因此能较为准确地估算结果。MCS算法有两种变形,即游走和蒙特卡洛积分。
如图所示,游走法就是随机选择一个起点,然后随机移动一步,以概率接受该步或返回原地。如果到达终点的概率较大,就可能收敛到某种结果。蒙特卡洛积分(MCI)也是一样的,但采用矩形区域,计算积分。
根据圆周率的公式,Pi=4Σ(1-1/p^2)(1-ln(1-pn)), p为任一[0,1]区间上的随机数。当近似于该概率时,可以用蒙特卡洛积分方法计算。假设在单位半径内有一个点的概率为pn,则圆周率的近似值为:
4Σ(1-1/p^2)(1-ln(1-pn)) ≈ Π(1-pn)pn⁻¹
当pn→1时,这条近似逐渐趋近于正确的圆周率值。使用蒙特卡洛积分法,需要确定随机点的分布,以及在积分区域中的面积。蒙特卡洛积分法的收敛性较好,但计算量较大。
# python实现蒙特卡洛模拟法生成随机数
import random
NUM_POINTS = 1000000
CIRCLE_RADIUS = 1.0
CENTER = (0.0, 0.0)
count = 0
points_inside = []
for i in range(NUM_POINTS):point = (random.uniform(-CIRCLE_RADIUS, CIRCLE_RADIUS), random.uniform(-CIRCLE_RADIUS, CIRCLE_RADIUS))dist = math.hypot(point[0]-CENTER[0], point[1]-CENTER[1])if dist <= CIRCLE_RADIUS:count += 1points_inside.append(point)
pi_estimate = count/NUM_POINTS * CIRCLE_RADIUS ** 2
print("Estimated Pi:", pi_estimate)
如上所述,蒙特卡洛模拟法生成随机数的基本思路是,在某个圆内部随机生成点,并计数这些点落入圆内的概率。估算圆周率的算法利用样本数据中的点的数量及位置,对比总的点数和概率,通过一定数量的计算,可以得到一个估计值。
3.Python中的随机数模块
1.概述
Python提供了random模块,可用于生成伪随机数。该模块提供以下功能:
- 随机数种子:可以用随机数种子来初始化随机数生成器,从而保证每次生成的随机数都一致。
- 随机数生成:random模块提供了四种随机数生成函数:rand()、randint()、choice()、shuffle()。
- 随机数分布:random模块还提供了常见的随机数分布,如均匀分布、正态分布、泊松分布、几何分布等。
本节将介绍random模块的常用方法。
2.random()方法
rand()方法用于生成[0.0,1.0)范围内的随机浮点数。该方法等价于random.uniform(0.0,1.0)。示例代码如下:
>>> import random
>>> random.seed(1) #设置随机数种子
>>> random.random()
0.7871366633560153
上例中,设置随机数种子为1,生成的随机数与你的机器生成的随机数是一致的。
3.randint()方法
randint()方法用于生成整数随机数。该方法需要两个参数:a、b,代表生成随机数的范围。该方法等价于random.randrange(a,b+1)。示例代码如下:
>>> import random
>>> random.seed(1) #设置随机数种子
>>> random.randint(1,100)
93
上例中,设置随机数种子为1,生成的随机整数范围为[1,100],包含端点值。
4.choice()方法
choice()方法用于从列表或元组中随机选择一个元素。该方法需要一个列表或元组作为参数。该方法等价于random.sample(population,k=1)[0]。示例代码如下:
>>> import random
>>> random.seed(1) #设置随机数种子
>>> fruits = ['apple','banana','orange']
>>> random.choice(fruits)
'orange'
上例中,设置随机数种子为1,从列表[‘apple’,‘banana’,‘orange’]中随机选择一个元素。
5.shuffle()方法
shuffle()方法用于将列表中的元素随机排序。该方法可以在列表中打乱顺序。该方法等价于random.shuffle(list)。示例代码如下:
>>> import random
>>> lst = [1, 2, 3, 4, 5]
>>> random.shuffle(lst)
>>> lst
[2, 5, 1, 4, 3]
上例中,将列表[1, 2, 3, 4, 5]随机打乱。
6.其他方法
random模块还有一些其他方法,如gauss()方法用于生成正态分布随机数,betavariate()方法用于生成Beta分布随机数,paretovariate()方法用于生成PARETO分布随机数等。这些方法需要参数,但一般情况下,默认参数就可以生成合理的随机数。
7.总结
本节介绍了random模块的常用方法,包括rand()、randint()、choice()、shuffle()等。这些方法可以生成随机数,但一般情况下,默认参数就可以生成合理的随机数。在日常生活中,随机数的应用非常广泛,如游戏中的保底机制、抽奖、抢红包、打乱顺子等。random模块提供了方便快捷的API,可以帮助开发者快速生成随机数。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
