(轉載)Math.random()隨機數的二三事

原文地址:http://www.soulteary.com/2014/07/05/js-math-random-trick.html

看到題目,如果大家在平時被問到:
如何生成一個怎么樣怎么樣的整數隨機數,估計大家都會不屑,但是當你淡定的回答:

獲取一個范圍應該是隨機數seeds和區間數值差的乘機與最小數相加然后再怎么怎么的時候…

有沒有發現你的思維已經固化了呢。

這個知識點應該是玩JS肯定會碰到的之一吧。

先來掉書袋,看看MDN的文檔
打開Node,進入終端命令行模式,輸入Math.random():

 Math.random()
 0.436846193857491

結果是不是依舊如同往常一樣稀松平常的小于1的一個偽隨機數跳了出來呢。
這個時候,如果別人問你,還有什么其他方案可以生成隨機數么,你會想到神馬呢。
如果你繼續在終端里輸入new Date()-0:

 new Date()-0
1404488829907

我想你可以得到一個自增的數字,對,就是“秒”,如果你說這貨哪里隨機了,請別著急:

 (new Date()-0)%10086
8657

這里的取模%的數值可以是大于2且最好小于當前時間的數值,則可以得到你取模數值概率分之一的概率的隨機數。

如果你取模的數值是隨機數呢,那么產生這個隨機數的可見的兩個變量都是隨機的,那么是不是近似真的“隨機數”了呢?

當然,如果使用這招,還要考慮到硬件以及語言執行過程的耗時,因為我們知道計算機執行的時候,有一個時間的精度的范疇,所以需要使用一點點的延時抑制。

扯了一些沒用的,你可能著急了,那么請保持好奇心,我們繼續說點無聊的事情。

Math.random會提供給我們一個[0,1)之間的隨機數,但是如果我們要[1,10]范圍隨機整數的話,可以使用以下三個函數:

  • Math.round
  • Math.ceil
  • Math.floor

我們先來生成一個隨機數:

 Math.random()*(10-1)+1
8.26644050120376

接著我們來使用這三個Math內建函數:

 Math.round(8.26644050120376)
8
 Math.ceil(8.26644050120376)
9
 Math.floor(8.26644050120376)
8

把數值換成8.56644050120376后,再來看看:

Math.round(8.56644050120376)
9
 Math.ceil(8.56644050120376)
9
 Math.floor(8.56644050120376)
8

所以區別一目了然,對于浮點數,round會遵守四舍五入規則,ceil無論如何貪心進位+1,floor無論如何都小心翼翼的自斷一臂-1,至于整數,自己試試看咯。

說到這里,接下來可以正常的描述內容了:

問:如何快速生成一段隨機文本,比如驗證碼或者我們訪問網站常見的隨機數token。

答案很多,我說一個經典的,其實思路很簡單,把剛剛生成隨機數的方法隨便選擇一個.toString():

Math.random().toString(36).substring(7);
//當然也可以寫成這樣
Math.random().toString(36).slice(2);
//或者利用時間
(new Date()-0).toString(36)

隨便輸出一些,我們可以看到這貨輸出的字符串長短參次不齊的:

mptzulnb3xr
87jx7vkuik9
761qsolayvi
amqx2mx6r
ce5uyvkuik9
5ioufim5cdi
dirp4hiwwmi
ioe597ldi
ohn9izfr
sprsakk2o6r
5g3ruo6flxr

//單純時間來做隨機是不是生成的慘不忍睹
//而且不做隨機延時抑制,重復太明顯

hx7pom3y
hx7pom3z
hx7pom40
hx7pom41
hx7pom42
hx7pom43
hx7pom44
hx7pom45

我們先來看看為什么用toString()可以生成隨機數:
首先前面的家伙不管是隨機數seeds生成的,還是時間遞增的長整型,它們都是Number構造器構造出來的[object Number],而在ecma.js中,Number的這個方法是這樣的:

/**
@param {Number} [radix]
@return {string}
*/
Number.prototype.toString = function(radix) {};

作為一個好人,我給你指條明路,MDN的文檔
看過之后是不是想到了parseInt
函數的第二個參數?我們發現,這個原生函數支持2~36(如果超出36,那么26個字母就不夠用了親)進制的轉換,所以如果在生成的時候,隨機切換進制,取結果的隨機位置,效果會不會更好呢,你可以試一試。
如果你想獲得字母多一點且平均一點,那么只有使用36進制了,但是不管怎么躲,都有可能出現一串數字。

xjuk0zxofen1xlxr

有沒有好方法來解決這個問題呢,答:

Math.random().toString(36).replace(/[^a-z]+/g, '')
//或者這樣
Math.random().toString(36).slice(2).replace(/\d/g, '')

這個時候,你或許會說,文章該就此結束了吧,這個方法看起來很爽很簡潔。
不過,你有思考過一個問題么,回顧前文,隨機數可以是0,1,...這些整數…

當隨機數是這些數值的時候,很抱歉,返回值是原來的數值,即0,1,...,我們得到的最后的結果就會是一個空字符串,而如果是0.5這類某些以5結尾的浮點數的時候,結果依然如此。還有當數值是某些時候,生成的隨機數位數會比較短…

解決這個問題,你當然可以重新生成這個隨機數,直到它輸出一個你心滿意足的隨機數再放過他,但是,我們剛剛了解到的生成一個大的隨機數的方法是依賴時間,小學還是初中學過的用一個比較小的數值除以一個比較大的數值,得到的結果是一個更小的浮點數來解決這個問題呢。

(Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1)

這樣就得到了一個比較長,且比較公平的隨機數。可以用node驗證一下:

var c = {}, r;
for (var i = 0, j = 10000000; i < j; i++) {
r = (Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1);
c[r] ? c[r] += 1 : c[r] = 1;}
for (var i in c) {if (c[i] === 1) {delete c[i];}}
console.log(c);

運行結果是沒有任何沖突,當然這可能是小概率事件。如果你覺得你點很背,你可以試一試下面這段,手動執行幾次,看看,有沒有不是空數組這個結果的結果。

for(var i = 0,j=100;i<j;i++){
(function(){var c = {}, r;for (var i = 0, j = 1000; i < j; i++) {
r = (Math.random() / +new Date()).toString(36).replace(/\d/g, '').slice(1);
c[r] ? c[r] += 1 : c[r] = 1;}
for (var i in c) {if (c[i] === 1) {delete c[i];}}
console.log(c);}())}

當然,你也可以不用這兩個測試例子上面的那段代碼,改用下面這種方式,多輸出幾次隨機字符串,然后拼合在一起。

for(var c = ''; c.length < 5;) c += Math.random().toString(36).substr(2)

這個把戲估計你看膩了,我們來看下面這個系列的示例,從固定的字典中抽取字符構成隨機字符串:

依賴Array的map方法,要注意兼容性,當然,你從MDN那邊copy一段hacks也可以無縫兼容,是不是看起來高大上一點:

Array.apply(0, Array(5)).map(function () {
    return (function (charset) {
        return charset.charAt(Math.floor(Math.random() * charset.length))
    }('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
}).join('');

不過或許你更容易接受這種多一點:

function rand(length, current) {
    current = current ? current : '';
    return length ? rand(--length, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 60)) + current) : current;
}

或者更傳統的:

function rand() {
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < 5; i++) text += possible.charAt(Math.floor(Math.random() * possible.length));
    return text;
}

或許你以為這樣把戲就玩完了,too young too simple,之前玩過了Number的原生函數,我們還可以動手腳的是String的原生函數,比如我們知道,字母的ASCII碼是[65,97],那么當這個區間是字典,我們隨機抽取這個區間是不是會有一些好玩的事情發生呢。

function rand(x) {
    var s = "";
    while (s.length < x && x > 0) {
        var r = Math.random();
        s += String.fromCharCode(Math.floor(r * 26) + (r > 0.5 ? 97 : 65));
    }
    return s;
}

隨便輸入一個電話號碼(推薦輸入短一點的數),然后等等看,是不是會出現下面的結果:

JrxKnxrHwJzGCKoKCzrpFxMxDBFqBrpAEGvpvMtCIHIHFCxtMxrHtpEGDyxzxwMBBqDvEBwxprwHqDMCErIzLwuyFApnxpxJoxBAFEoHsAEqvIxBIJvpFBoLnvurHJsvDFwGtFvDsELMLwzowvqBJtCwrGCsCHGvsxCLunGxtrtnyvvwyFqEsstotxnsrqLHAIyCxzLxDqtuzsoFJAGHyxrwxJusJrtpuvIyIILoJrGLKnptqHLBAKwGEpnIwzCtFAnrHIqLHynGwuyupsDpLJFGxBuJBouwCDGKsKFCLKnAzoupsxFqIynKFCoBApyBsJKzJpKwEFCyGywrBoFpvorMzBrBAFowrKxvuJLoKtzpEoDsEsBExyGBMssnADnBwrvJDGunJsMyFGCxIFApEnoLyGxBrHroBsLAICGLDIwvqp

這個話題,好像說的有點麻木了,換個需求吧。有的網站會玩一些隨機背景色的小花樣。有沒有優雅的解決方案呢:

遵守隨機數函數的定義,獲取顏色數值之間的數值就好,看過了上面的代碼,這句,很好懂了伐:

'#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6);

當然,你可以寫一個更加直觀的方案:

//用十進制數據來代替16進制數據運算,請注意因為右邊是開區間,要比上面的0xffffff +1,最后將結果轉換回十六進制,然后修剪一下字符串,輸出
Math.floor(Math.random() * 16777216).toString(16);
'#000000'.slice(0, -color.length) + color;

//或者更簡短的樣子如下
"#" + ("000000" + Math.floor(Math.random() * 16777216).toString(16)).substr(-6);

還有一類花樣,如下:

function randomColor() {
    var r = function () { return Math.floor(Math.random()*256) };
    return "rgb(" + r() + "," + r() + "," + r() + ")";
}

輸出如下:

rgb(29,236,191)

時間不早了,最后說一下隨機排序數組吧。

關于洗牌算法,網上流傳很多,隨便選擇一種模擬一下就好,比如隨便寫的全重排:

var i = 0, data = [], r;
for (; i < 10; data[i++] = i);
while (--i) {
    r = Math.round(Math.random() * 9 + 1) - 1;
    data[i] = data[i] + data[r], data[r] = data[i] - data[r], data[i] = data[i] - data[r];
}
console.log(data)

或者利用Array.prototype.sort()函數,這里可以不把里面的數值帶進來運算。

首先Math.random()會生成一個[0,1)之間的數值,用0.5這個比較公平的數值減去它,概率得到小于0,等于0,大于0三種狀況,而Array.prototype.sort()期待的數值恰好是[-1,0,1],是不是很省事。

var i = 0, data = [], r;
for (; i < 10; data[i++] = i);
data.sort(function () {
    return .5 - Math.random();
});
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容

  • 方法1 (數據類型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math...
    GB_speak閱讀 41,082評論 2 6
  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創建并操作數組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,262評論 0 4
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,799評論 18 139
  • 1. 常用那幾種瀏覽器測試?有哪些內核(Layout Engine)?(Q1)瀏覽器:IE,Chrome,Fire...
    猿分讓我們相遇閱讀 247評論 0 0
  • 那是柱子最酷的一晚,單槍匹馬,手舉板凳,大腳破門,以一敵四,如天神下凡,把徐峰一伙打得嗷嗷直叫,就像高舉大劍怒吼著...
    阿姆斯特高閱讀 835評論 9 5