簡體   English   中英

Haskell中的偽隨機數生成器

[英]Pseudorandom number generators in Haskell

我正在研究最新的編程Praxis謎題解決方案 - 第一個實現最小標准隨機數生成器, 第二個實現一個隨機播放盒與一個或不同的偽隨機數生成器一起使用。 實現數學非常簡單。 對我來說,棘手的一點是弄清楚如何正確地將各個部分組合在一起。

從概念上講,偽隨機數生成器是一個函數stepRandom :: s -> (s, a)其中s是生成器內部狀態的類型, a是生成的隨機選擇對象的類型。 對於線性同余PRNG,我們可以有s = a = Int64 ,例如,或者s = Int64a = Double 這篇關於PSE的文章非常好地展示了如何使用monad通過隨機計算來線程化PRNG狀態,並使用runRandom完成runRandom以運行具有特定初始狀態(種子)的計算。

從概念上講,shuffle box是一個函數shuffle :: box -> a -> (box, a)以及一個用PRNG中的值初始化所需大小的新框的函數。 然而,在實踐中,這個盒子的表示有點棘手。 為了提高效率,它應該表示為一個可變數組,它會強制它進入STIO 有點模糊的東西:

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 Vectors 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]

請注意,這些部件的實際操作並不十分復雜。 不幸的是,在類型STmwc-randomvector ,和pipes都各自獨立地高度概括,因此可以說是相當繁重的,在第一到理解。 希望以上,我故意削弱並專門針對這個確切的問題專門研究每種類型,將更容易理解並提供一些直覺,了解每個這些精彩的庫如何單獨和一起工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM