试图在Haskell中找出`random`函数

时间:2013-06-25 03:19:32

标签: haskell random

我刚刚了解了random功能。

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

我试图生成随机Bool值。为了做到这一点,我创建了一个函数randomBool

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

然后我发现True s比False s少了很多。 我很好奇,并检查如下

> length $ takeWhile randomBool [1..]
53667

我认为这意味着对于前53667个正整数,random . mkStdGen会返回True,这对我来说似乎不是很随机。 这很正常吗?或者我做错了什么让True更容易发生?

3 个答案:

答案 0 :(得分: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 monad或其他东西手动设置它,或者只使用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可能是一本有用的读物​​。

答案 1 :(得分:3)

计算机具有确定性,无法生成随机数。相反,它们依赖于返回数字分布的数学公式,这些数字看起来是随机的。这些被称为伪随机数生成器。但是,由于确定性,我们遇到的问题是,如果我们在每次调用程序时以相同的方式运行这些公式,我们将获得相同的随机数生成器。显然,这不好,因为我们希望我们的数字是随机的!因此,我们必须为随机生成器提供从一次运行到另一次运行的初始种子值。对于大多数人(即那些不做密码填充的人),随机数生成器由当前时间播种。在Haskell中,这个伪随机生成器由StdGen类型表示。 mkStdGen函数用于创建带种子的随机数生成器。与C不同,在Haskell中有一个全局随机数生成器,您可以拥有任意多个,并且可以使用不同的种子创建它们。

但是,有一点需要注意:由于这些数字是伪随机的,因此无法保证使用不同种子创建的随机数生成器会返回与另一种相比看起来随机的数字。这意味着当您致电randomBool并为其提供连续的种子值时,无法保证从您创建的StdGen获得的数字是随机的StdGen与其继承者相比。这就是你得到近50000 True的原因。

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

以下是一个示例,生成三个随机bool,abc

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]。要使用Bool的起始种子生成x的无限列表,只需运行randoms (mkStdGen x)即可。您可以使用length $ takeWhile id (randoms (mkStdGen x))实现示例。您应该验证是否为x的不同初始值获得了不同的值,但如果您提供相同的x,则始终使用相同的值。

最后,如果你不关心被绑定到IO monad,Haskell还提供了一个全局随机数生成器,就像命令式语言一样。在randomIO monad中调用函数IO将为您提供您喜欢的任何类型的随机值(只要它是Random类型类的实例,至少)。除了myRandom monad之外,您可以使用与IO类似的方法。这具有额外的便利性,它被Haskell运行时预先播种,这意味着您甚至不必担心创建StdGen。因此,要在Bool monad中创建10个IO的随机列表,您只需replicateM 10 randomIO :: IO [Bool].

希望这会有所帮助:)

答案 2 :(得分:2)

mkStdGen创建的随机生成器不一定会生成随机值作为其第一个结果。要生成下一个随机数,请使用上一次random调用返回的随机生成器。

例如,此代码生成10 Bool s。

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