2013-06-25 30 views
12

我刚刚了解到random函数。试图找出哈斯克尔的`随机`功能

据我的理解,random函数采用一个类型的值,这是一个实例RandomGen并返回一个随机值,我们可以指定它的值。另一方面,mksdGen需要Int并生成random函数所需的随机生成器。

我试图生成随机Bool值。为了做到这一点,我做了一个功能randomBool

randomBool :: Int -> Bool 
randomBool = fst . random . mkStdGen 

然后我发现了很多True总比False s的小数字。 我很好奇,并检查如下

> length $ takeWhile randomBool [1..] 
53667 

我认为,这意味着第一个53667个的正整数,random . mkStdGen回报True,这似乎并没有很随意给我。 这很正常吗?或者我做错了什么使True更容易发生?

回答

2

通过mkStdGen创建的随机数发生器不一定产生一个随机值作为它的第一个结果。要生成下一个随机数,请使用前面random调用返回的随机数发生器。

例如,该代码生成10 Bool秒。

take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool] 
+9

为了详细说明这一点:随机发生器不能保证有随机分布在种子值上,但它保证给出产生值序列的随机分布。 – mange

16

通俗地说,当你调用mkStdGen与附近的种子一起,你会得到两个“相似”的发电机。在你的例子中,你实际上是为每个提供的种子创建新的生成器,并且由于这些种子是1,2,3等,他们将产生类似的流。

当你调用random有发电机,你实际上是在对的第二个元素得到一个新的发电机:

Prelude System.Random> random (mkStdGen 100) :: (Bool, StdGen) 
(True,4041414 40692) 

所以一个好主意,就是用这个提供发电机您对random下一个电话。即,

Prelude System.Random> let (i, gen0) = random (mkStdGen 100) :: (Bool, StdGen) 
Prelude System.Random> let (j, gen1) = random gen0   :: (Bool, StdGen) 
Prelude System.Random> let (k, gen2) = random gen1   :: (Bool, StdGen) 
Prelude System.Random> (i, j, k) 
(True, False, False) 

因此,为了使一堆随机值,要通过发电机作为国家。您可以通过State单子什么的手动设置的,或者仅使用randoms函数,它处理通过发电机状态您:

Prelude System.Random> take 10 $ randoms (mkStdGen 100) :: [Bool] 
[True,False,False,False,False,True,True,False,False,True] 

如果不特别在意在IO是(它发生),可以使用randomIO

Prelude System.Random> import Control.Monad 
Prelude System.Random Control.Monad> replicateM 10 randomIO :: IO [Bool] 
[True,True,False,True,True,False,False,False,True,True] 

LYAH的This section可能是一个有用的阅读。

+0

谢谢你的回答。在阅读了LYAH后,我发现在几乎所有情况下的第一枚硬币都是“真”。 (当我检查了几千个案例时,我得到了比'False'更多的'True')嗯...尽管'close'种子产生了'相似'的发生器,但我仍然觉得'True'显示在一排中连续出现5万个数字... – Tengu

+1

问题在于那些从1-50000接种的〜50000发生器是相似的,这样他们每个产生的第一个元素就是“真”。试试'random(mkStdGen 100000)::(Bool,StdGen)'来观察一个最初产生一个'False'的生成器。 关键是你应该真的只*种子*一个单一的发电机,而不是种一堆具有相似值的发电机。使用任何你想要的种子,然后使用“生成的生成器”生成附加的随机值。 – jtobin

3

计算机是确定性的,不能生成随机数。相反,他们依靠数学公式来返回随机数字的分布。这些被称为伪随机数发生器。但是,由于确定性,我们有一个问题,即如果我们在每次调用程序期间以相同的方式运行这些公式,我们将得到相同的随机数生成器。显然,这是不好的,因为我们希望我们的数字是随机的!因此,我们必须为随机生成器提供一个初始种子值,该值随逐次运行而变化。对于大多数人(即那些不做密码的人),随机数发生器按当前时间播种。在Haskell中,这个伪随机生成器由StdGen类型表示。函数mkStdGen用于创建一个带有种子的随机数生成器。与C不同的是,在Haskell中有一个全局随机数生成器,你可以拥有尽可能多的,你可以用不同的种子创建它们。

但是,有个警告:由于数字是伪随机数,因此不能保证用不同种子创建的随机数发生器返回的数字与其他数字相比看起来是随机的。这意味着,当您致电randomBool并给予其连续的种子值时,不能保证您从StdGen获得的数字是随机的,而与其后续版本接种的StdGen是随机的。这就是为什么你拿到差不多50000 True的原因。

为了获得实际上看起来随机的数据,您需要继续使用相同的随机数生成器。如果您注意,random Haskell函数的类型为StdGen -> (a, StdGen)。因为Haskell是纯粹的,所以random函数需要一个随机数生成器,生成一个伪随机值(返回值的第一个元素),然后返回一个新的StdGen,它表示使用原始种子播种的生成器,但准备给出新的随机数。您需要保留这个其他的StdGen,并将它传递给下一个random函数以获得随机数据。

这里是一个例子,生成三个随机布尔,a,bc

randomBools :: StdGen -> (Bool, Bool, Bool) 
randomBools gen = let (a, gen') = random gen 
         (b, gen'') = random gen'' 
         (c, gen''') = random gen''' 
        in (a, b, c) 

注意如何gen变量“线程”,通过电话随机。

您可以通过使用状态monad来简化传递状态。例如,

import Control.Monad.State 
import System.Random 

type MyRandomMonad a = State StdGen a 

myRandom :: Random a => MyRandomMonad a 
myRandom = do 
    gen <- get -- Get the StdGen state from the monad 
    let (nextValue, gen') = random gen -- Generate the number, and keep the new StdGen 
    put gen' -- Update the StdGen in the monad so subsequent calls generate new random numbers 
    return nextValue 

现在你可以写randomBools功能:

randomBools' :: StdGen -> (Bool, Bool, Bool) 
randomBools' gen = fst $ runState doGenerate gen 
    where doGenerate = do 
      a <- myRandom 
      b <- myRandom 
      c <- myRandom 
      return (a, b, c) 

如果你想生成的Bool个(有限)列表中,你可以做

randomBoolList :: StdGen -> Int -> ([Bool], StdGen) 
randomBoolList gen length = runState (replicateM length myRandom) gen 

请注意,我们如何返回StdGen作为返回对的第二个元素,以允许将它赋予新函数。

更简单地说,如果您只想从StdGen生成相同类型的随机值的无限列表,则可以使用randoms函数。这有签名(RandomGen g, Random a) => g -> [a]。要使用起始种子x生成无限列表Bool,只需运行randoms (mkStdGen x)。你可以使用length $ takeWhile id (randoms (mkStdGen x))来实现你的例子。您应该验证您获得了不同初始值x的不同值,但如果您提供相同的值x,则值始终相同。最后,如果你不关心被绑定到monod,单元,Haskell还提供了一个全局的随机数生成器,很像命令式语言。在IO monad中调用函数randomIO将为您提供任意类型的随机值(至少为Random typeclass的实例)。除了IO monad之外,您可以使用上面的myRandom。这增加了方便,它由Haskell运行时预先播种,这意味着您不必担心创建StdGen。因此,在IO单子创造的10 Bool个随机列表,所有你需要做的就是replicateM 10 randomIO :: IO [Bool].

希望这有助于:)

+0

严格来说,如果您有合适的硬件资源,计算机_can_会生成随机数。最新的英特尔处理器甚至为此添加了机器代码指令......(是的,这只与您试图创建的点相关。) – MathematicalOrchid