[英]Pseudorandom number generators in Haskell
我正在研究最新的編程Praxis謎題的解決方案 - 第一個實現最小標准隨機數生成器, 第二個實現一個隨機播放盒與一個或不同的偽隨機數生成器一起使用。 實現數學非常簡單。 對我來說,棘手的一點是弄清楚如何正確地將各個部分組合在一起。
從概念上講,偽隨機數生成器是一個函數stepRandom :: s -> (s, a)
其中s
是生成器內部狀態的類型, a
是生成的隨機選擇對象的類型。 對於線性同余PRNG,我們可以有s = a = Int64
,例如,或者s = Int64
和a = Double
。 這篇關於PSE的文章非常好地展示了如何使用monad通過隨機計算來線程化PRNG狀態,並使用runRandom
完成runRandom
以運行具有特定初始狀態(種子)的計算。
從概念上講,shuffle box是一個函數shuffle :: box -> a -> (box, a)
以及一個用PRNG中的值初始化所需大小的新框的函數。 然而,在實踐中,這個盒子的表示有點棘手。 為了提高效率,它應該表示為一個可變數組,它會強制它進入ST
或IO
。 有點模糊的東西:
mkShuffle :: (Integral i, Ix i, MArray a e m) => i -> m e -> m (a i e) mkShuffle size getRandom = do thelist <- replicateM (fromInteger.fromIntegral $ size) getRandom newListArray (0,size-1) thelist shuffle :: (Integral b, Ix b, MArray a b m) => a b b -> b -> m b shuffle box n = do (start,end) <- getBounds box let index = start + n `quot` (end-start+1) value <- readArray box index writeArray box index n return value
然而,我真正想做的是將 (初始化的?)shuffle框附加到PRNG,以便將PRNG的輸出“管道”輸入到隨機盒中。 我不明白如何正確設置管道。
我假設目標是實現如下算法:我們有一種隨機生成器,我們可以想到它以某種方式產生一個隨機值流
import Pipes
prng :: Monad m => Producer Int m r
-- produces Ints using the effects of m never stops, thus the
-- return type r is polymorphic
我們想通過隨機盒修改此PRNG。 Shuffle box有一個可變狀態Box
,它是一個隨機整數數組,它們以特定方式修改隨機整數流
shuffle :: Monad m => Box -> Pipe Int Int m r
-- given a box, convert a stream of integers into a different
-- stream of integers using the effects of m without stopping
-- (polymorphic r)
shuffle
在逐個整數的基礎上工作,通過以模塊大小為模的輸入隨機值索引到其Box
,在那里存儲輸入值,並發出先前存儲在那里的值。 在某種意義上,它就像一個隨機延遲函數。
因此,通過該規范,我們可以實現真正的實現。 我們想使用一個可變數組,所以我們將使用vector
庫和ST
monad。 ST
要求我們繞過一個幽靈s
參數,在整個特定匹配ST
單子調用,所以當我們寫Box
,它會需要公開該參數。
import qualified Data.Vector.Mutable as Vm
import Control.Monad.ST
data Box s = Box { sz :: Int, vc :: Vm.STVector s Int }
該sz
參數是大小Box
的內存和Vm.STVector s
是一個可變的ST
Vector
掛s
ST
線程。 我們可以立即使用它來構建我們的shuffle算法,現在知道Monad
m
實際上必須是ST s
。
import Control.Monad
shuffle :: Box s -> Pipe Int Int (ST s) r
shuffle box = forever $ do -- this pipe runs forever
up <- await -- wait for upstream
next <- lift $ do let index = up `rem` sz box -- perform the shuffle
prior <- Vm.read (vc box) index -- using our mutation
Vm.write (vc box) index up -- primitives in the ST
return prior -- monad
yield next -- then yield the result
現在我們只想將這個shuffle
附加到一些prng
Producer
。 由於我們使用vector
因此使用高性能的mwc-random
庫很不錯。
import qualified System.Random.MWC as MWC
-- | Produce a uniformly distributed positive integer
uniformPos :: MWC.GenST s -> ST s Int
uniformPos gen = liftM abs (MWC.uniform gen)
prng :: MWC.GenST s -> Int -> ST s (Box s)
prng gen = forever $ do
val <- lift (uniformPos gen)
yield val
請注意,由於我們正在傳遞PRNG種子MWC.GenST s
,因此在ST s
線程中我們不需要捕獲修改並將它們連接起來。 相反, mwc-random
在幕后使用可變的STRef s
。 另請注意,我們修改MWC.uniform
僅返回正索引,因為這是我們在shuffle
的索引方案所必需的。
我們也可以使用mwc-random
來生成我們的初始框。
mkBox :: MWC.GenST s -> Int -> ST s (Box s)
mkBox gen size = do
vec <- Vm.replicateM size (uniformPos gen)
return (Box size vec)
這里唯一的技巧是非常好的Vm.replicateM
函數,它有效地具有約束類型
Vm.replicateM :: Int -> ST s Int -> Vm.STVector s Int
其中第二個參數是ST s
動作,它生成向量的新元素。
最后我們有了所有的部分。 我們只需要組裝它們。 幸運的是,我們使用pipes
獲得的模塊化使這一點變得微不足道。
import qualified Pipes.Prelude as P
run10 :: MWC.GenST s -> ST s [Int]
run10 gen = do
box <- mkBox gen 1000
P.toListM (prng gen >-> shuffle box >-> P.take 10)
在這里,我們使用(>->)
構建生產管道,並使用P.toListM
來運行該管道並生成列表。 最后我們只需要在IO
執行這個ST s
線程,這也是我們可以創建初始MWC.GenST s
種子並使用MWC.withSystemRandom
將其提供給run10
MWC.withSystemRandom
,它生成初始種子,如SystemRandom
。
main :: IO ()
main = do
result <- MWC.withSystemRandom run10
print result
我們有管道。
*ShuffleBox> main
[743244324568658487,8970293000346490947,7840610233495392020,6500616573179099831,1849346693432591466,4270856297964802595,3520304355004706754,7475836204488259316,1099932102382049619,7752192194581108062]
請注意,這些部件的實際操作並不十分復雜。 不幸的是,在類型ST
, mwc-random
, vector
,和pipes
都各自獨立地高度概括,因此可以說是相當繁重的,在第一到理解。 希望以上,我故意削弱並專門針對這個確切的問題專門研究每種類型,將更容易理解並提供一些直覺,了解每個這些精彩的庫如何單獨和一起工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.