隨機數(我指的是偽隨機數)是通過顯式或隱式的狀態來生成的。這意味著在 haskell 中,隨機數的使用(通過system.random
庫)是伴隨著狀態的傳遞的。
大部分需要獲得幫助的人都有命令式程式設計的背景,因此,我會先用命令式的方式,然後再用函式式的方式來教大家在 haskell 中使用隨機數。
我會生成滿足以下條件的隨機列表:
在 io monad 中有乙個全域性的生成器,你可以初始化它,然後獲取隨機數。下面有一些常用的函式:
初始化或者設定全域性生成器,我們可以用mkstdgen
來生成隨機種子。因此,有乙個很傻瓜式的用法:
setstdgen (mkstdgen 42)
當然,你可以用任意的int
來替換42
。
其實,你可以選擇是否呼叫setstdgen
,如果你不呼叫的話,全域性的生成器還是可用的。因為在 runtime 會在啟動的時候用乙個任意的種子去初始化它,所以每次啟動的時候,都會有乙個不同的種子。
在給定範圍隨機返回乙個型別為a
的值,同時全域性生成器也會更新。你可以通過乙個元組來指定範圍。下面這個例子會返回a
到z
之間的隨機值(包含a
和z
):
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
作為隨機種子,返回a
到z
之間包含a
和z
的無限列表:
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]
猜猜結果,由於透明引用,這兩個列表的結果是一樣的!(如果你想用乙個隨機種子來生成兩個不同的列表,我等下告訴你乙個方法)。
下面一種方法來完成建立1
到7
的隨機列表:
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
的無限列表。擷取第乙個值,並擴大這個值到1
到7
,然後用剩下的列表來生成答案。換句話說,把輸入的列表分成(r:rs)
,r
決定生成列表的長度(1
到7
),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 會相對好點,但也不算理想。個...