譯 Haskell 中隨機數的使用

2021-09-19 11:02:44 字數 4316 閱讀 6221

隨機數(我指的是偽隨機數)是通過顯式或隱式的狀態來生成的。這意味著在 haskell 中,隨機數的使用(通過system.random庫)是伴隨著狀態的傳遞的。

大部分需要獲得幫助的人都有命令式程式設計的背景,因此,我會先用命令式的方式,然後再用函式式的方式來教大家在 haskell 中使用隨機數。

我會生成滿足以下條件的隨機列表:

在 io monad 中有乙個全域性的生成器,你可以初始化它,然後獲取隨機數。下面有一些常用的函式:

初始化或者設定全域性生成器,我們可以用mkstdgen來生成隨機種子。因此,有乙個很傻瓜式的用法:

setstdgen (mkstdgen 42)
當然,你可以用任意的int來替換42

其實,你可以選擇是否呼叫setstdgen,如果你不呼叫的話,全域性的生成器還是可用的。因為在 runtime 會在啟動的時候用乙個任意的種子去初始化它,所以每次啟動的時候,都會有乙個不同的種子。

在給定範圍隨機返回乙個型別為a的值,同時全域性生成器也會更新。你可以通過乙個元組來指定範圍。下面這個例子會返回az之間的隨機值(包含az):

c <- randomrio ('a', 'z')
a可以是任意型別嗎?並非如此。在 haskell 98 標準中,random庫只支援bool,char,int,integer,float,double(你可以自己去擴充套件這個支援的範圍,但這是另外乙個話題了)。

返回乙個型別為a的隨機數(a可以是任意型別嗎?看上文),全域性的生成器也會更新。下面這個例子會返回乙個double型別的隨機數:

x <- randomio :: io double
隨機數返回的範圍由型別決定。

需要注意的是,這些都是 io 函式,因此你只可以在 io 函式中使用它們。換句話說,如果你寫了乙個要使用它們的函式,它的返回型別也會變成是 io 函式。

舉個例子,上面提到的**片段都要寫在do block中。這只是乙個提醒,因為我們想要用命令式的方式來生成隨機數。

下面這個例子展示如何在 io monad 中完成之前的任務:

import system.random

main = do

setstdgen (mkstdgen 42) -- 這步是可選的,如果有這一步,你每一次執行的結果都是一樣的,因為隨機種子固定是 42

s <- randomstuff

print s

randomstuff :: io [float]

randomstuff = do

n <- randomrio (1, 7)

sequence (replicate n (randomrio (0, 1)))

你可能有以下原因想知道如何用函式式的方式生成隨機數:

實際上,有兩種方法來用函式式的方式去生成隨機數:

這裡有一些常用的函式用來建立生成器和包含隨機數的無限列表。

用隨機種子建立生成器。

用生成器生成給定範圍的無限列表。例子:用42作為隨機種子,返回az之間包含az的無限列表:

randomrs ('a', 'z') (mkstdgen 42)
型別a是隨機數的型別。型別g看起來是通用的,但實際上它總是stdgen

用給定的生成器生成隨機數的無限列表。例如:用42作為隨機種子生成double型別的列表:

randoms (mkstdgen 42) :: [double]
隨機數的範圍由型別決定,你需要查文件來確定具體範圍,或者直接用randomrs

注意,這些都是函式式的 —— 意味著這裡面沒有***,特別是生成器並不會更新。如果你用乙個生成器去生成第乙個列表,然後用相同的生成器去生成第二個列表...

g = mkstdgen 42

a = randoms g :: [double]

b = randoms g :: [double]

猜猜結果,由於透明引用,這兩個列表的結果是一樣的!(如果你想用乙個隨機種子來生成兩個不同的列表,我等下告訴你乙個方法)。

下面一種方法來完成建立17的隨機列表:

import system.random

main = do

let g = mkstdgen 42

let [s] = take 1 (randomstuff g)

print s

randomstuff :: randomgen g => g -> [[float]]

randomstuff g = work (randomrs (0.0, 1.0) g)

work :: [float] -> [[float]]

work (r:rs) =

let n = truncate (r * 7.0) + 1

(xs, ys) = splitat n rs

in xs : work ys

除了必要的列印操作外,這是純函式式的。它用生成器生成了無限列表,然後再用這個無限列表來生成另乙個無限列表作為答案,最後取第乙個作為返回值。

我這樣做是因為儘管我們今天的人物是生成乙個隨機數,但你通常會需要很多個,我希望這個例子可以對你有點幫助。

上面的**的工作原理是:用乙個生成器,建立乙個包含float的無限列表。擷取第乙個值,並擴大這個值到17,然後用剩下的列表來生成答案。換句話說,把輸入的列表分成(r:rs)r決定生成列表的長度(17),rs之後會被計算答案。

用乙個隨機種子建立兩個不同的生成器,其他情況下重用相同的種子是不明智的。

g = mkstdgen 42

(ga, gb) = split g

-- do not use g elsewhere

如果你想建立多餘兩個的生成器,你可以對新的生成器中的其中乙個使用split

g = mkstdgen 42

(ga, g') = split g

(gb, gc) = split g'

-- do not use g, g' elsewhere

我們可以用split來獲得兩個生成器,這樣我們就可以產生兩個隨機列表了。

randomstuff :: randomgen g => g -> [[float]]

randomstuff g = work (randomrs (1, 7) ga) (randomrs (0.0, 1.0) gb)

where (ga,gb) = split g

work :: [int] -> [float] -> [[float]]

work (n:ns) rs =

let (xs,ys) = splitat n rs

in xs : work ns ys

它把生成器分成兩個,然後產生兩個列表。

我在主程式中硬編碼了隨機種子。正常情況下你可以在其他地方獲取隨機種子 —— 從輸入中獲取,從檔案中獲取,從時間上獲取,或者從某些裝置中獲取。

這些在主程式中都是 do-able 的,因為它們都可以在 io monad 中訪問。

你也可以通過getstdgen獲取全域性生成器:

main = do

g <- getstdgen

let [s] = take randomstuff g

print s

原文

隨機數使用

函式rnd 是乙個非常重要的函式。如果你想建立乙個隨機的問候語,乙個日期的隨機提示,或者甚至乙個遊戲,你將要使用這個函式。函式rnd 返回乙個0到1之間的隨機數。這裡有這個函式的乙個例子及其可能的返回值 rnd 0.7055643 典型情況下,你更感興趣的是用這個函式來返回處在一定範圍內的整數。要返...

c 中隨機數的使用

在.framework中提供了乙個專門用來產生隨機數的類system.random,使用這個類時必須匯入system命名空間。當然,命名空間system在每個asp.頁面中都是自動匯入的,所以我們可以直接使用這個類。計算機不可能產生完全隨機的數字,所謂的隨機數發生器都是通過一定的演算法對事先選定的隨...

ios中的隨機數使用

有如下三種隨機數方法 1.srand unsigned time 0 int i rand 5 2.srandom time 0 int i random 5 3.int i arc4random 5 常用 注 rand 實際並不是乙個真正的偽隨機數發生器,random 會相對好點,但也不算理想。個...