[英]Trying to figure out `random` function in Haskell
我剛學會了random
函數。
據我所知, random
函數接受一個類型的值,它是RandomGen
一個實例,並返回一個隨機值,我們可以指定它的值。 另一方面, mksdGen
采用Int
並生成random
函數需要的隨機生成器。
我試圖生成隨機Bool
值。 為了做到這一點,我做了一個函數randomBool
。
randomBool :: Int -> Bool
randomBool = fst . random . mkStdGen
然后我發現了更多的True
不是False
的小數字。 我很好奇,並檢查如下
> length $ takeWhile randomBool [1..]
53667
我認為這意味着對於第一個53667正整數, random . mkStdGen
random . mkStdGen
返回True
,對我來說似乎不是很隨機。 這很正常嗎? 或者我做錯了什么讓True
更容易發生?
非正式地,當您使用接近在一起的種子調用mkStdGen
時,您將獲得兩個“相似”的生成器。 在您的示例中,您實際上是為每個提供的種子創建新的生成器,並且由於這些種子是1,2,3等,它們將產生類似的流。
當您使用生成器調用random
時,實際上會在對的第二個元素中返回一個新生成器:
Prelude System.Random> random (mkStdGen 100) :: (Bool, StdGen)
(True,4041414 40692)
所以一個好主意是使用這個提供的生成器來進行下一次random
調用。 也就是說,
Prelude System.Random> let (i, gen0) = random (mkStdGen 100) :: (Bool, StdGen)
Prelude System.Random> let (j, gen1) = random gen0 :: (Bool, StdGen)
Prelude System.Random> let (k, gen2) = random gen1 :: (Bool, StdGen)
Prelude System.Random> (i, j, k)
(True, False, False)
因此,要制作一堆隨機值,您希望將生成器作為狀態傳遞 。 您可以通過State
monad或其他東西手動設置它,或者只使用randoms
函數,它處理為您傳遞生成器狀態:
Prelude System.Random> take 10 $ randoms (mkStdGen 100) :: [Bool]
[True,False,False,False,False,True,True,False,False,True]
如果您不特別關心IO
(它會發生),您可以使用randomIO
:
Prelude System.Random> import Control.Monad
Prelude System.Random Control.Monad> replicateM 10 randomIO :: IO [Bool]
[True,True,False,True,True,False,False,False,True,True]
LYAH的這一部分可能是一本有用的讀物。
計算機是確定性的,不能生成隨機數。 相反,它們依賴於返回數字分布的數學公式,這些數字看起來是隨機的。 這些被稱為偽隨機數生成器。 但是,由於確定性,我們遇到的問題是,如果我們在每次調用程序時以相同的方式運行這些公式,我們將獲得相同的隨機數生成器。 顯然,這不好,因為我們希望我們的數字是隨機的! 因此,我們必須為隨機生成器提供從一次運行到另一次運行的初始種子值。 對於大多數人(即那些不做密碼填充的人),隨機數生成器按當前時間播種。 在Haskell中,這個偽隨機生成器由StdGen
類型表示。 mkStdGen
函數用於創建帶種子的隨機數生成器。 與C不同,在Haskell中有一個全局隨機數生成器,您可以擁有任意多個,並且可以使用不同的種子創建它們。
但是,有一點需要注意:由於這些數字是偽隨機的,因此不能保證使用不同種子創建的隨機數生成器會返回與另一種相比看起來隨機的數字。 這意味着,當你調用randomBool
,並給它連續的種子值,也不能保證你從一開始數StdGen
相比,您創建是隨機的StdGen
其繼任者接種。 這就是你獲得近50000 True
的原因。
為了獲得實際看起來隨機的數據,您需要繼續使用相同的隨機數生成器。 如果您注意到, random
Haskell函數的類型為StdGen -> (a, StdGen)
。 因為Haskell是純粹的, random
函數采用隨機數生成器,生成偽隨機值(返回值的第一個元素)然后返回一個新的StdGen
,它表示用原始種子播種的生成器,但是准備給出一個新的隨機數。 您需要保留其他StdGen
並將其傳遞給下一個random
函數以獲取隨機數據。
這是一個例子,產生三個隨機bool, a
, b
和c
。
randomBools :: StdGen -> (Bool, Bool, Bool)
randomBools gen = let (a, gen') = random gen
(b, gen'') = random gen''
(c, gen''') = random gen'''
in (a, b, c)
注意gen
變量是如何通過隨機調用“線程化”的。
您可以使用狀態monad簡化傳遞狀態。 例如,
import Control.Monad.State
import System.Random
type MyRandomMonad a = State StdGen a
myRandom :: Random a => MyRandomMonad a
myRandom = do
gen <- get -- Get the StdGen state from the monad
let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen
put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers
return nextValue
現在您可以將randomBools
函數編寫為:
randomBools' :: StdGen -> (Bool, Bool, Bool)
randomBools' gen = fst $ runState doGenerate gen
where doGenerate = do
a <- myRandom
b <- myRandom
c <- myRandom
return (a, b, c)
如果你想生成一個(有限的) Bool
列表,你可以做到
randomBoolList :: StdGen -> Int -> ([Bool], StdGen)
randomBoolList gen length = runState (replicateM length myRandom) gen
注意我們如何將StdGen
作為返回對的第二個元素返回,以允許它被賦予新函數。
更簡單地說,如果您只想從StdGen
生成相同類型的隨機值的無限列表,則可以使用randoms
函數。 這有簽名(RandomGen g, Random a) => g -> [a]
。 要使用x
的起始種子生成無限的Bool
列表,只需運行randoms (mkStdGen x)
。 您可以使用length $ takeWhile id (randoms (mkStdGen x))
實現您的示例。 您應該確認您得到的不同的初始值不同的值x
,但始終不變的值,如果你提供同樣的x
。
最后,如果你不關心與IO
monad綁定,Haskell還提供了一個全局隨機數生成器,就像命令式語言一樣。 在IO
monad中調用函數randomIO
將為您提供您喜歡的任何類型的隨機值(只要它是Random
類型類的實例,至少)。 除了IO
monad之外,你可以myRandom
上面的myRandom
一樣使用它。 這為Haskell運行時預先播種提供了額外的便利,這意味着您甚至不必擔心創建StdGen
。 因此,要在IO
monad中創建10個Bool
的隨機列表,您所要做的就是replicateM 10 randomIO :: IO [Bool].
希望這可以幫助 :)
由mkStdGen
創建的隨機生成器不一定會生成隨機值作為其第一個結果。 要生成下一個隨機數,請使用上一個random
調用返回的隨機生成器。
例如,此代碼生成10個Bool
。
take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.