Haskell中的伪随机数生成器

时间:2014-01-19 03:26:32

标签: haskell random

我正在研究最新的编程实践谜题的解决方案 - the first关于实现最小标准随机数生成器和the second关于实现一个随机播放框以与一个或不同的伪随机数一起使用数字生成器。实现数学非常简单。对我来说,棘手的一点是弄清楚如何正确地将各个部分放在一起。

从概念上讲,伪随机数生成器是函数stepRandom :: s -> (s, a),其中s是生成器内部状态的类型,a是生成的随机选择对象的类型。对于线性同余PRNG,我们可以有s = a = Int64,或者s = Int64a = Double。 PSE上的This post非常好地展示了如何使用monad通过随机计算来线程化PRNG状态,并使用runRandom完成任务以运行具有特定初始状态的计算(种子) )。

从概念上讲,随机播放框是一个函数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的输出“管道”到shuffle框中。我不明白如何正确设置管道。

1 个答案:

答案 0 :(得分:4)

我假设目标是实现如下算法:我们有一种随机生成器,我们可以想到它以某种方式生成随机值流

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。随机播放盒具有可变状态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 monad调用中匹配的幻像ST参数,因此当我们编写Box时,它需要公开该参数。< / p>

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

请注意,由于我们在MWC.GenST s线程中传递了PRNG种子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来运行该管道并生成列表。最后,我们只需要在ST s中执行此IO个帖子,这也是我们可以创建初始MWC.GenST s种子的地方,并使用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-randomvectorpipes中的各种类型都是高度概括的,因此理解起来非常繁琐首先。希望上面,我故意削弱并专门针对这个确切的问题专门研究每一种类型,这将更容易理解,并提供一些直觉,了解每个这些精彩的库如何单独和一起工作。