python学习笔记 --- 随机数进阶
你真的懂隨機(jī)數(shù)?
Author : Jasper Yang
School : Bupt
Q:為什么要寫這篇文章?
A:因?yàn)槲野l(fā)現(xiàn)在最近的科學(xué)計(jì)算中,常常遇到隨機(jī)數(shù),所有的隨機(jī)數(shù)都是基于0,1隨機(jī),而這個(gè)0,1隨機(jī)怎么實(shí)現(xiàn)呢?下面我會(huì)娓娓道來~
這篇文章不同于網(wǎng)路上的雜散的技術(shù)文,我是針對(duì) random 這么一個(gè)論題展開調(diào)研最后將所有相關(guān)的知識(shí)進(jìn)行整理敘述,希望每個(gè)人看完都可以得到小小的提升~
& 什么是隨機(jī)數(shù)
隨機(jī)數(shù):數(shù)學(xué)上產(chǎn)生的都是偽隨機(jī)數(shù),真正的隨機(jī)數(shù)使用物理方法產(chǎn)生的
隨機(jī)數(shù)種子:隨機(jī)數(shù)的產(chǎn)生是由算術(shù)規(guī)則產(chǎn)生的,在c++中,srand(seed)的隨機(jī)數(shù)種子不同,rand()的隨機(jī)數(shù)值就不同,倘若每次的隨機(jī)數(shù)種子一樣,則rand()的值就一樣。所以要產(chǎn)生隨機(jī)數(shù),則srand(seed)的隨機(jī)數(shù)種子必須也要隨機(jī)的。在 python 中就是 random.seed()來設(shè)置種子。
下面我講的隨機(jī)數(shù)不僅僅講隨機(jī)數(shù)生成的原理,也會(huì)講在python中以及在c++中怎么去實(shí)現(xiàn),當(dāng)然,大部分資料也都是網(wǎng)上找的,我只是做了一個(gè)整理匯總,并用自己的語(yǔ)言加以敘述。
& 隨機(jī)數(shù)的原理
這里我看了一篇博客,由于這篇博客是那個(gè)博主轉(zhuǎn)的,但是該博主并沒有表明是從哪里轉(zhuǎn)來的,我就不po出鏈接了,大家往下看~
有位朋友問那博主關(guān)于一段程序的錯(cuò)誤。
C/C++ code for (int i =0;i< n;++i) {srand((unsigned)time( NULL )); int r = rand()%100;cout << r << ","; }這里很明顯他是想輸出一串小于100的隨機(jī)的數(shù)列.可是運(yùn)行結(jié)果輸出的卻是類似 97,97,97,97,....97,30,30,30,30,30,30,30,30,30,30,30,30,....,27,27,27,27,27,27,....的序列.很明顯這樣完全看不出有任何的隨機(jī)性.這是由于他對(duì)C的rand函數(shù)不理解導(dǎo)致的錯(cuò)誤用法.而這兩天逛C#區(qū)我也同樣看到了幾個(gè)類似的錯(cuò)誤用法(C和C#的rand從大體的原理上差不多).想想自己初學(xué)的時(shí)候類似的錯(cuò)誤犯得也不少.所以自己下去查了寫資料總結(jié)了在隨機(jī)數(shù)使用上的一些錯(cuò)誤的用法.希望能對(duì)初學(xué)者有所幫助。
現(xiàn)在各種語(yǔ)言中的隨機(jī)數(shù)產(chǎn)生函數(shù)所產(chǎn)生的"隨機(jī)數(shù)",實(shí)際上被稱之為"偽隨機(jī)數(shù)".可以將
整個(gè)隨機(jī)數(shù)函數(shù)看做這樣一個(gè)表達(dá)式:
$$A = R(s)$$
其中R是隨機(jī)函數(shù),s是種子.A是一個(gè)數(shù)列.即對(duì)于任意一個(gè)種子s,經(jīng)過R的計(jì)算后,總有一個(gè)確定的數(shù)列A與之對(duì)應(yīng).而當(dāng)在C#里調(diào)用var rnd = new Random (s)或在C里調(diào)用srand(s)實(shí)質(zhì)上所做工作之一就是設(shè)定這個(gè)種子.而rnd.Next();或rand()只不過是在A上取下一個(gè)元素而已.當(dāng)然實(shí)際的實(shí)現(xiàn)不可能事先計(jì)算一個(gè)數(shù)列A,所以rand()相當(dāng)于由s計(jì)算出下一個(gè)數(shù)字s',然后將s'作為新的種子賦值給s,最后將s'作為結(jié)果返回。
往細(xì)了講,就是這樣。
如果約定:$a_1=f(seed),a_{n+1}=f(an)$
那你可以行到一個(gè)序列:$a_1,a_2,a_3...a_n$,那么要制作一個(gè)偽隨機(jī)函數(shù)rand,只需要讓它每調(diào)用一次就返回序列的下一個(gè)元素就行。
下面是兩種常見的錯(cuò)誤做法
C# code for (int i=0;i<n;++i) {var rnd = new Random (s);//s是實(shí)先確定的一個(gè)數(shù)字Console.Write ("{0},",rnd.Next()); }這樣,每次使用Random,都去申請(qǐng)了一個(gè)變量rnd,然后才用這個(gè)變量去找隨機(jī)數(shù)(rnd.Next())。這樣其實(shí)就是在隨機(jī)數(shù)的序列中總是在找第一個(gè)。這樣下來,第一個(gè)數(shù)肯定是固定的,就不存在什么隨機(jī)數(shù)了。
第二種情況更加常見。
C# code for (int i=0;i<n;++i) {var rnd = new Random ();//用系統(tǒng)時(shí)間作為種子Console.Write ("{0},",rnd.Next()); }之前的第一種情況使用了一個(gè)固定的常數(shù)s來做種子,這里選用了系統(tǒng)時(shí)間做種子,想要達(dá)到隨機(jī)的效果,但是得到的結(jié)果往往就會(huì)是和博主那位朋友一樣的結(jié)果97,97,97,97,....97,30,30,30,30,30,30,30,30,30,30,30,30,....,27,27,27,27,27,27,.... 。
這是因?yàn)閃indows系統(tǒng)時(shí)鐘的更新頻率大概在10ms左右.而這個(gè)for循環(huán)的執(zhí)行顯然要快
得多.于是在一段執(zhí)行時(shí)間內(nèi)Environment.TickCount (Random的默認(rèn)種子)或是C的time函數(shù)返回的都是同一個(gè)值.從而導(dǎo)致rnd.Next在一段時(shí)間內(nèi)返回一個(gè)常數(shù)。
所以正確的做法應(yīng)該是把種子移出循環(huán)之外。
C# code var rnd = new Random ();//用系統(tǒng)時(shí)間作為種子 for (int i=0;i<n;++i) {Console.Write ("{0},",rnd.Next()); }各種庫(kù)中是怎么實(shí)現(xiàn)隨機(jī)數(shù)呢?
在 Linux 下實(shí)現(xiàn)的方式類似如下
myrand、mysrand分別對(duì)應(yīng)rand和srand,但實(shí)際的rand實(shí)現(xiàn)會(huì)復(fù)雜一些。
下面是這位博主實(shí)現(xiàn)的方式,其實(shí)挺簡(jiǎn)單的,我們每個(gè)人都可以實(shí)現(xiàn)一種自己想要的隨機(jī)數(shù)方式加到自己的私有庫(kù)中~
** Copyright (c) 2008 Microsoft::Tsorgy.Utils, Reserved.* * Filename: @(#)Random.cs* Create by: TsOrgY* Email: tsorgy@gmail.com* Date: 2008/12/27 15:01:40* * Classname: Random* Description: 一種能夠產(chǎn)生滿足某些隨機(jī)性統(tǒng)計(jì)要求的數(shù)字序列的設(shè)備.* */ using System; using System.Runtime.InteropServices; namespace Tsorgy.Utils {/// <summary>/// 表示偽隨機(jī)數(shù)生成器,一種能夠產(chǎn)生滿足某些隨機(jī)性統(tǒng)計(jì)要求的數(shù)字序列的設(shè)備./// </summary>[Serializable][ComVisible(true)]public class Random {private int inext;private int inextp;private const int MBIG = 0x7fffffff;private const int MSEED = 0x9a4ec86;private const int MZ = 0;private int[] SeedArray;/// <summary>/// 使用與時(shí)間相關(guān)的默認(rèn)種子值,初始化 Random 類的新實(shí)例./// </summary>public Random(): this(Environment.TickCount) {}/// <summary>/// 使用指定的種子值初始化 System.Random 類的新實(shí)例./// </summary>/// <param name="Seed">用來計(jì)算偽隨機(jī)數(shù)序列起始值的數(shù)字。如果指定的是負(fù)數(shù),則使用其絕對(duì)值。</param>/// <exception cref="System.OverflowException">Seed 為 System.Int32.MinValue,在計(jì)算其絕對(duì)值時(shí)會(huì)導(dǎo)致溢出。</exception>public Random(int Seed) {this.SeedArray = new int[0x38];int num2 = 0x9a4ec86 - Math.Abs(Seed);this.SeedArray[0x37] = num2;int num3 = 1;for (int i = 1; i < 0x37; i++) {int index = (0x15 * i) % 0x37;this.SeedArray[index] = num3;num3 = num2 - num3;if (num3 < 0) {num3 += 0x7fffffff;}num2 = this.SeedArray[index];}for (int j = 1; j < 5; j++) {for (int k = 1; k < 0x38; k++) {this.SeedArray[k] -= this.SeedArray[1 + ((k + 30) % 0x37)];if (this.SeedArray[k] < 0) {this.SeedArray[k] += 0x7fffffff;}}}this.inext = 0;this.inextp = 0x15;Seed = 1;}private double GetSampleForLargeRange() {int num = this.InternalSample();if ((((this.InternalSample() % 2) == 0) ? 1 : 0) != 0) {num = -num;}double num2 = num;num2 += 2147483646.0;return (num2 / 4294967293);}private int InternalSample() {int inext = this.inext;int inextp = this.inextp;if (++inext >= 0x38) {inext = 1;}if (++inextp >= 0x38) {inextp = 1;}int num = this.SeedArray[inext] - this.SeedArray[inextp];if (num < 0) {num += 0x7fffffff;}this.SeedArray[inext] = num;this.inext = inext;this.inextp = inextp;return num;}/// <summary>/// 返回非負(fù)隨機(jī)數(shù)./// </summary>/// <returns>大于或等于零且小于 System.Int32.MaxValue 的 32 位帶符號(hào)整數(shù)。</returns>public virtual int Next() {return this.InternalSample();}/// <summary>/// 返回一個(gè)小于所指定最大值的非負(fù)隨機(jī)數(shù)./// </summary>/// <param name="maxValue">要生成的隨機(jī)數(shù)的上界(隨機(jī)數(shù)不能取該上界值)。maxValue 必須大于或等于零。</param>/// <returns>大于或等于零且小于 maxValue 的 32 位帶符號(hào)整數(shù),即:返回的值范圍包括零但不包括 maxValue。</returns>/// <exception cref="System.ArgumentOutOfRangeException">maxValue 小于零。</exception>public virtual int Next(int maxValue) {if (maxValue < 0) {throw new ArgumentOutOfRangeException("maxValue", string.Format("'{0}' must be greater than zero.", maxValue));}return (int) (this.Sample() * maxValue);}/// <summary>/// 返回一個(gè)指定范圍內(nèi)的隨機(jī)數(shù)./// </summary>/// <param name="minValue">返回的隨機(jī)數(shù)的下界(隨機(jī)數(shù)可取該下界值)。</param>/// <param name="maxValue">返回的隨機(jī)數(shù)的上界(隨機(jī)數(shù)不能取該上界值)。maxValue 必須大于或等于 minValue。</param>/// <returns>一個(gè)大于或等于 minValue 且小于 maxValue 的 32 位帶符號(hào)整數(shù),即:返回的值范圍包括 minValue 但不包括 maxValue。如果minValue 等于 maxValue,則返回 minValue。</returns>/// <exception cref="System.ArgumentOutOfRangeException">minValue 大于 maxValue。</exception>public virtual int Next(int minValue, int maxValue) {if (minValue > maxValue) {throw new ArgumentOutOfRangeException("minValue", string.Format("'{0}' cannot be greater than {1}.", minValue, maxValue));}long num = maxValue - minValue;if (num <= 0x7fffffffL) {return (((int) (this.Sample() * num)) + minValue);}return (((int) ((long) (this.GetSampleForLargeRange() * num))) + minValue);}/// <summary>/// 用隨機(jī)數(shù)填充指定字節(jié)數(shù)組的元素./// </summary>/// <param name="buffer">包含隨機(jī)數(shù)的字節(jié)數(shù)組。</param>/// <exception cref="System.ArgumentNullException">buffer 為 null。</exception>public virtual void NextBytes(byte[] buffer) {if (buffer == null) {throw new ArgumentNullException("buffer");}for (int i = 0; i < buffer.Length; i++) {buffer[i] = (byte) (this.InternalSample() % 0x100);}}/// <summary>/// 返回一個(gè)介于 0.0 和 1.0 之間的隨機(jī)數(shù)./// </summary>/// <returns>大于或等于 0.0 而小于 1.0 的雙精度浮點(diǎn)數(shù)字。</returns>public virtual double NextDouble() {return this.Sample();}/// <summary>/// 返回一個(gè)介于 0.0 和 1.0 之間的隨機(jī)數(shù)./// </summary>/// <returns>大于或等于 0.0 而小于 1.0 的雙精度浮點(diǎn)數(shù)字。</returns>protected virtual double Sample() {return (this.InternalSample() * 4.6566128752457969E-10);}} }這里我要另外提到一個(gè)大家聽到了很多次的東西 ------------> 線性同余法
這也是實(shí)現(xiàn)隨機(jī)數(shù)的一種方式
線性同余方法(LCG)
它的遞歸公式:
$$N_{j+1} = (A * N_j +B) (mod M)$$
其中A,B,M是產(chǎn)生器設(shè)定的常數(shù)。
LCG的周期最大為M,但大部分情況都會(huì)少于M。要令LCG達(dá)到最大周期,應(yīng)符合以下條件:
B,M互質(zhì)
M的所有質(zhì)因子的積能整除A-1
若M是4的倍數(shù),A-1也是
A,B,$N_0$都比M小
A,B是正整數(shù)
最后生成的就是一個(gè) <$N_i$> 序列,這個(gè)序列應(yīng)該滿足下面的幾個(gè)條件。
這個(gè)函數(shù)應(yīng)該是一個(gè)完整周期的產(chǎn)生函數(shù)。也就是說,這個(gè)函數(shù)應(yīng)該在重復(fù)之前產(chǎn)生出0 到m之間的所有數(shù)
產(chǎn)生的序列應(yīng)該看起來是隨機(jī)的
這個(gè)函數(shù)應(yīng)該用32bit 算術(shù)高效實(shí)現(xiàn)
實(shí)現(xiàn)
#include <stdio.h> #include <time.h> static unsigned long rand_seed; void mysrand (unsigned long int); void myrand (); int main (void) { int i; mysrand (time (NULL)); for (i = 0; i < 100; i++) { myrand (); } return 0; } void mysrand (unsigned long seed) { rand_seed = seed; } void myrand () { rand_seed = (rand_seed * 16807L) % ((1 << 31) - 1); printf ("%ld ", rand_seed); }可以看到,這個(gè)實(shí)現(xiàn)和上面提到的 linux 的實(shí)現(xiàn)很像,其實(shí)就是一樣的。
& 隨機(jī)數(shù)使用
因?yàn)樽罱玫腸++和python特別的多(我覺得這兩個(gè)語(yǔ)言是程序員們最需要掌握的兩種語(yǔ)言,別的都是補(bǔ)充 ~:)),所以下面我就只講這兩種語(yǔ)言的實(shí)現(xiàn)方式。
c++
實(shí)例程序
#include "stdafx.h" #include <time.h> #include <stdlib.h> int _tmain(int argc, _TCHAR* argv[]) {// 初始化隨機(jī)數(shù)種子// time函數(shù)返回從1970年1月1日零時(shí)零分零秒到目前為止所經(jīng)過的時(shí)間,單位為秒srand((int)time(NULL));int j;for (int i = 0; i < 10; i++) {j = (rand() * 10) / RAND_MAX + 1; // 生成1~10之間的隨機(jī)數(shù)printf("j = %d \n", j);}unsigned start = (rand() * 1000)/ RAND_MAX + 15550; // 生成15550~16549之間的隨機(jī)數(shù)printf("start = %d \n", start);start &= ~1; // 把start變?yōu)榕紨?shù),如果是奇數(shù),則start變?yōu)閟tart - 1的偶數(shù)printf("start = %d \n", start);getchar();return 0; }c++ 其實(shí)就是 srand 和 rand 兩個(gè)函數(shù)。
上面的都只是生成的整數(shù),如果需要浮點(diǎn)數(shù)什么的就需要自己再加以處理,而在python中提供了比較多的函數(shù)。
python
這塊的內(nèi)容是 Capricorn的實(shí)驗(yàn)室的整理。其實(shí)這塊內(nèi)容直接去官網(wǎng)的doc翻譯就可以了,但是我有點(diǎn)懶,不太想去看了,就用了這篇博文的內(nèi)容~
<h3>random.random</h3>
random.random()用于生成一個(gè)0到1的隨機(jī)符點(diǎn)數(shù): 0 <= n < 1.0
<h3>random.uniform</h3>
random.uniform的函數(shù)原型為:random.uniform(a, b),用于生成一個(gè)指定范圍內(nèi)的隨機(jī)符點(diǎn)數(shù),兩個(gè)參數(shù)其中一個(gè)是上限,一個(gè)是下限。如果a>b,則生成的隨機(jī)數(shù)n: a <= n <= b。如果 $a<b$, 則 b <= n <= a。
<h3>random.randint</h3>
random.randint()的函數(shù)原型為:random.randint(a, b),用于生成一個(gè)指定范圍內(nèi)的整數(shù)。其中參數(shù)a是下限,參數(shù)b是上限,生成的隨機(jī)數(shù)n: a <= n <= b。
<h3>random.randrange</h3>
random.randrange 的函數(shù)原型為:random.randrange([start], stop[, step]),從指定范圍內(nèi),按指定基數(shù)遞增的集合中 獲取一個(gè)隨機(jī)數(shù)。如:random.randrange(10, 100, 2),結(jié)果相當(dāng)于從[10, 12, 14, 16, ... 96, 98]序列中獲取一個(gè)隨機(jī)數(shù)。random.randrange(10, 100, 2)在結(jié)果上與 random.choice(range(10, 100, 2) 等效。
<h3>random.choice</h3>
random.choice從序列中獲取一個(gè)隨機(jī)元素。其函數(shù)原型為:random.choice(sequence)。參數(shù)sequence表示一個(gè)有序類型。這里要說明 一下:sequence在python不是一種特定的類型,而是泛指一系列的類型。list, tuple, 字符串都屬于sequence。有關(guān)sequence可以查看python手冊(cè)數(shù)據(jù)模型這一章。下面是使用choice的一些例子:
print random.choice("學(xué)習(xí)Python") print random.choice(["JGood", "is", "a", "handsome", "boy"]) print random.choice(("Tuple", "List", "Dict"))<h3>random.shuffle</h3>
random.shuffle的函數(shù)原型為:random.shuffle(x[, random]),用于將一個(gè)列表中的元素打亂
<h3>random.sample</h3>
random.sample的函數(shù)原型為:random.sample(sequence, k),從指定序列中隨機(jī)獲取指定長(zhǎng)度的片斷。sample函數(shù)不會(huì)修改原有序列。
OK,告一段落了~,朋友們,有沒有覺得進(jìn)步了一點(diǎn)點(diǎn)呢~
paper done 2017/05/13總結(jié)
以上是生活随笔為你收集整理的python学习笔记 --- 随机数进阶的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mybatis 返回 插入的主键
- 下一篇: illegal multibyte se