我需要用于模拟的小型高斯随机数列表,所以我尝试了以下内容:
import System.Random
seed = 10101
gen = mkStdGen seed
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
这只是Box-Muller算法 - 给定r1,r2在[0,1]区间内的均匀随机数,它返回一个高斯随机数。
normals 0 g = []
normals n g = take n $ map (boxMuller 0 1) $ pairs $ randoms g
where pairs (x:y:zs) = (x,y):(pairs zs)
每次我需要我的随机数列表时,我都使用了这个normals
函数。
问题必须明显:它始终生成相同的序列,因为我总是使用相同的种子!我没有得到新的序列,我只是一直得到序列的前n个值。
我明确假装的是,当我输入时:
x = normal 10
y = normal 50
我要将x作为map (boxMuller 0 1) $ pairs $ randoms g
的前10个值,将y作为此列表中的下50个值,依此类推。
当然这是不可能的,因为在给定相同输入的情况下,函数必须始终返回相同的值。我如何逃脱这个陷阱?
答案 0 :(得分:27)
我认为你的计算需要在monad中抽象生成器的随机数是最干净的。这是代码的样子:
我们将把StdGen实例放在一个状态monad中,然后在状态monad的get和set方法上提供一些糖来给我们随机数字。
首先,加载模块:
import Control.Monad.State (State, evalState, get, put)
import System.Random (StdGen, mkStdGen, random)
import Control.Applicative ((<$>))
(通常我可能不会指定导入,但这样可以很容易地理解每个函数的来源。)
然后我们将定义我们的“需要随机数的计算”monad;在这种情况下,State StdGen
的别名称为R
。 (因为“随机”和“兰德”已经意味着别的东西。)
type R a = State StdGen a
R的工作方式是:定义一个需要随机数流(monadic“action”)的计算,然后用runRandom
“运行”:
runRandom :: R a -> Int -> a
runRandom action seed = evalState action $ mkStdGen seed
这将采取操作和种子,并返回操作的结果。就像通常的evalState
,runReader
等一样。
现在我们只需要州立大学附近的糖。我们使用get
来获取StdGen,我们使用put
来安装新状态。这意味着,为了得到一个随机数,我们会写:
rand :: R Double
rand = do
gen <- get
let (r, gen') = random gen
put gen'
return r
我们得到随机数生成器的当前状态,用它来获取一个新的随机数和一个新的生成器,保存随机数,安装新的生成器状态,并返回随机数。
这是一个可以使用runRandom运行的“动作”,所以让我们试试吧:
ghci> runRandom rand 42
0.11040701265689151
it :: Double
这是一个纯函数,因此如果使用相同的参数再次运行它,您将得到相同的结果。杂质留在你传递给runRandom的“动作”中。
无论如何,你的函数想要成对的随机数,所以让我们写一个动作来产生随机数的对:
randPair :: R (Double, Double)
randPair = do
x <- rand
y <- rand
return (x,y)
使用runRandom运行此选项,您会看到对中有两个不同的数字,正如您所期望的那样。但请注意,您不必提供带有参数的“rand”;这是因为函数是纯粹的,但“rand”是一个动作,它不一定是纯粹的。
现在我们可以实现您的normals
功能。 boxMuller
就像你在上面定义的那样,我只是添加了一个类型签名,这样我就可以理解发生了什么,而不必阅读整个函数:
boxMuller :: Double -> Double -> (Double, Double) -> Double
boxMuller mu sigma (r1,r2) = mu + sigma * sqrt (-2 * log r1) * cos (2 * pi * r2)
在实现所有辅助函数/动作后,我们最终可以编写normals
,一个0参数的动作,返回一个(懒惰生成的)无限正常分布的双精度列表:
normals :: R [Double]
normals = mapM (\_ -> boxMuller 0 1 <$> randPair) $ repeat ()
如果你想要,你也可以不那么简洁地写这个:
oneNormal :: R Double
oneNormal = do
pair <- randPair
return $ boxMuller 0 1 pair
normals :: R [Double]
normals = mapM (\_ -> oneNormal) $ repeat ()
repeat ()
给monadic动作一个无限的无任何动作来调用函数(并且是法线结果无限长的原因)。我最初写过[1..]
,但我重写了它以从程序文本中删除无意义的1
。我们不是在整数上运行,我们只想要一个无限的列表。
无论如何,就是这样。要在真实程序中使用它,您只需在R动作中执行需要随机法线的工作:
someNormals :: Int -> R [Double]
someNormals x = liftM (take x) normals
myAlgorithm :: R [Bool]
myAlgorithm = do
xs <- someNormals 10
ys <- someNormals 10
let xys = zip xs ys
return $ uncurry (<) <$> xys
runRandom myAlgorithm 42
编程monadic动作的常用技巧适用; 1}}尽可能少地保留你的申请,事情也不会太乱。
哦,另一方面:懒惰可以干净地“泄漏”在monad边界之外。这意味着你可以写:
R
它会起作用。
答案 1 :(得分:6)
从randoms
获得的列表是无限的,当你使用有限前缀时,你不需要扔掉无限的尾巴。您可以将随机数作为附加参数传递,并将未使用的随机数作为附加结果返回,或者您可以将无限序列的随机数存储在状态monad中。
编译器和其他需要提供唯一符号的代码也会出现类似的问题。这只是Haskell真正的痛苦,因为你在整个代码中处理状态(随机数生成器或符号生成器)。
我已经完成了具有显式参数和monad的随机算法,并且没有一个真正令人满意。如果你修改monad我可能会建议使用一个包含尚未使用的无限随机数列表的状态monad。
答案 2 :(得分:1)
您也可以使用newStdGen
来回避问题,然后每次都会得到一个不同的种子(虚拟)。