Haskell中的伪随机数发生器

Haskell中的伪随机数发生器,haskell,random,Haskell,Random,我正在研究最新编程实践难题的解决方案——实现最小标准随机数生成器,以及实现与该生成器或不同的伪随机数生成器配套的随机框。实现数学非常简单。对我来说,棘手的一点是弄清楚如何正确地将各个部分组合在一起 从概念上讲,伪随机数生成器是一个函数stepRandom::s->(s,a),其中s是生成器内部状态的类型,而a是随机选择的生成对象的类型。例如,对于线性全等PRNG,我们可以有s=a=Int64,或者可能有s=Int64和a=Double。在PSE上,它很好地展示了如何使用monad通过随机计算来执

我正在研究最新编程实践难题的解决方案——实现最小标准随机数生成器,以及实现与该生成器或不同的伪随机数生成器配套的随机框。实现数学非常简单。对我来说,棘手的一点是弄清楚如何正确地将各个部分组合在一起

从概念上讲,伪随机数生成器是一个函数
stepRandom::s->(s,a)
,其中
s
是生成器内部状态的类型,而
a
是随机选择的生成对象的类型。例如,对于线性全等PRNG,我们可以有
s=a=Int64
,或者可能有
s=Int64
a=Double
。在PSE上,它很好地展示了如何使用monad通过随机计算来执行PRNG状态,并使用
runRandom
以特定初始状态(种子)来运行计算

从概念上讲,随机框是一个函数
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 mkShuffle::(积分i,Ix i,MArray a e m)=>i->me->m(a i e) mkShuffle size getRandom=do 列表a b->b->m b 洗牌盒n=do
(开始,结束)我假设目标是实现如下算法:我们有一个某种类型的随机生成器,我们可以认为它以某种方式产生了一个随机值流

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
在整数对整数的基础上工作,方法是将传入的随机值按框的大小进行模化,将传入值存储在框中,然后将先前存储在框中的值发送到其
框中。在某种意义上,它就像一个随机延迟函数


有了这个规范,让我们开始真正的实现。我们希望使用可变数组,因此我们将使用
vector
库和
ST
monad
ST
要求我们传递一个幻像
s
参数,该参数在整个特定
ST
monad调用中匹配,因此当我们编写
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
是链接到
s
ST
线程的可变
ST
向量。现在知道
Monad
m
实际上必须是
sts
,我们可以立即使用它来构建洗牌算法

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
请注意,由于我们在
ST s
线程中传递PRNG种子,
MWC.GenST 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)
其中,第二个参数是生成向量新元素的
ST s
操作


最后我们有了所有的碎片。我们只需要组装它们。幸运的是,我们通过使用
管道
获得的模块性使这变得微不足道

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)
我们有我们的管道

*ShuffleBox> main
[743244324568658487,8970293000346490947,7840610233495392020,6500616573179099831,1849346693432591466,4270856297964802595,3520304355004706754,7475836204488259316,1099932102382049619,7752192194581108062]


请注意,这些部件的实际操作并不是非常复杂。不幸的是,
ST
mwc random
vector
pipes
中的类型都是各自高度概括的,因此一开始理解起来可能会很麻烦。希望上面我故意弱化和专门化了几乎每种类型来解决这个确切问题的内容,这将更容易理解,并为这些出色的库如何单独和一起工作提供一点直觉。

您确定您真的需要可变数组提供的效率吗?一个未绑定但不可变的数组可能就足够了。@icktoofay,我确信这对于一个小盒子来说是很好的,或者对于一个编译器可以理解为以单线程方式使用的盒子来说是很好的,但是在这一点上,我对这个概念比对应用程序更感兴趣。哪个概念?可变数组还是“管道”?每一个都可以很容易地单独演示,但结合起来可能会很复杂。如果目标是用随机框修改PRNG的输出,那么我在
mwc random
vector
的可变向量上写了几行这样做,但是这种机制比这里的要复杂得多。@J.Abrahamson,管道,特别是如何将其与可变数组组合在一起。不幸的是,我可能还没有真正做好准备,我想:/。Haskell的故事倾向于“当然,我明白。好的,没问题。当然。简单。等等!WTF?这到底是什么意思?”“我会继续,然后用
vector
mwc random
管道发布一个回复。我相信它能满足你的需求,如果你手头有一些非常通用的类型,阅读起来并不难。我至少需要一两天的时间来消化它。如果我最后明白了,我会很乐意接受这个答案。请注意,使用高质量的PRNG根本不是重点。关键是能够插入任何我想要的PRNG。有一件事让我有点担心:从概念上讲,一个PRNG和一个shu
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)
main :: IO ()
main = do
  result <- MWC.withSystemRandom run10
  print result
*ShuffleBox> main
[743244324568658487,8970293000346490947,7840610233495392020,6500616573179099831,1849346693432591466,4270856297964802595,3520304355004706754,7475836204488259316,1099932102382049619,7752192194581108062]