還記得先前提到 Math.random
並非是純函式嗎,因為每次給定相同的輸入都會是不同的輸出回傳回來,那有什麼辦法可以讓它是回傳亂數,又可以是純函數呢? 沒錯,PRNG亂數生成器!在用 State Monad 實作前,先來用一般的方式實作看看!
簡單來說 PRNG 的概念就是先給定一個數值,稱為 seed, 將其放入 generator
中,取得一個 nextSeed 之後,又可以將 nextSeed 放入 generator
產生新的亂數。
const generator = (seed) => (seed * 1103515245 + 12345) & 0x7fffffff;
const seed = 1;
const randomNumber1 = generator(seed); // 1103527590
const randomNumber2 = generator(randomNumber1) // 377401600
而我們寫的 generator 能確保每次給定相同輸入就會有相同輸出!
有這個能幹嘛呢? 我們可以用它來實作一個給定特定範圍,就會隨機回傳特定範圍數值的函式
const value = (seed) => (seed >>> 16) / 0x7fff;
const normalize = (min, max) => (x) => Math.floor(x * (max - min)) + min;
const randomInRange = (min, max) => (seed) => {
const nextSeed = generator(seed);
const random = R.compose(normalize(min, max), value)(nextSeed);
return [nextSeed, random];
};
這樣就可以隨機產生特定範圍的亂數,而由於我們還需要用下一個 seed 的值去造下一組數,所以必須將下一個 seed 也一併傳出,
const seed = 1;
const [n1, r1] = randomInRange(0, 10)(seed) // 5
const [n2, r2] = randomInRange(0, 10)(n1) // 1
既然都可以亂數產生特定範圍的數值了,也可以用此一算法,去隨機取出陣列中某一個項目
const name = ["jing", "jing*5", "jing-tech"];
const randomName = (from) => (seed) => {
const [n1, r1] = randomInRange(seed, 0, from.length)
return [n1, name[r1]];
}
const [n1, name] = randomName(name)(1) // '1103527590, jing*5'
大致介紹完 PRNG 的概念了,可以反思一下如果現在要從某種陣列(資料)內抽取很多組不同的值呢? 又或是想要一次用同一組亂數不同陣列(資料)取值呢? 這樣是不是會讓程式複雜度上升呢?
沒錯,本章就先介紹到這裡,下一章帶大家來看 State Monad 是如何處理這種問題的!
感謝大家閱讀!