我正在研究最新的编程实践谜题的解决方案 - the first关于实现最小标准随机数生成器和the second关于实现一个随机播放框以与一个或不同的伪随机数一起使用数字生成器。实现数学非常简单。对我来说,棘手的一点是弄清楚如何正确地将各个部分放在一起。
从概念上讲,伪随机数生成器是函数stepRandom :: s -> (s, a)
,其中s
是生成器内部状态的类型,a
是生成的随机选择对象的类型。对于线性同余PRNG,我们可以有s = a = Int64
,或者s = Int64
和a = Double
。 PSE上的This post非常好地展示了如何使用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
然而,我真正想做的是将一个(初始化的?)shuffle框附加到PRNG,以便将来自PRNG的输出“管道”到shuffle框中。我不明白如何正确设置管道。
答案 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]
请注意,这些部件的实际操作并不十分复杂。不幸的是,ST
,mwc-random
,vector
和pipes
中的各种类型都是高度概括的,因此理解起来非常繁琐首先。希望上面,我故意削弱并专门针对这个确切的问题专门研究每一种类型,这将更容易理解,并提供一些直觉,了解每个这些精彩的库如何单独和一起工作。