12

我刚刚了解了random函数。

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

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

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

然后我发现小数字True的 s 比s 多得多。False我对此感到好奇并检查如下

> length $ takeWhile randomBool [1..]
53667

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

4

3 回答 3

16

非正式地,当您调用mkStdGen靠近在一起的种子时,您将获得两个“相似”的生成器。在您的示例中,您实际上正在为提供的每个种子创建新的生成器,并且由于这些种子是 1、2、3 等,它们将产生类似的流。

当您random使用生成器调用时,您实际上会在该对的第二个元素中返回一个新生成器:

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

所以一个好主意是使用这个提供的生成器来下次调用random. IE,

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 或其他方式手动设置它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 的这一部分可能是有用的读物​​。

于 2013-06-25T03:55:00.153 回答
3

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

但是,有一个警告:由于数字是伪随机的,因此不能保证使用不同种子创建的随机数生成器返回的数字与另一个相比看起来是随机的。这意味着当您调用randomBool并给它连续的种子值时,不能保证您从创建的数字中获得的数字与带有其后继StdGen的种子相比是随机的。StdGen这就是为什么你得到近 50000True的原因。

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

这是一个示例,生成三个随机布尔值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变量是如何通过对 random 的调用“线程化”的。

您可以使用 state 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)

如果要生成Bools 的(有限)列表,可以执行

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

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

希望这可以帮助 :)

于 2013-06-25T19:02:10.857 回答
2

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

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

take 10 $ unfoldr (Just . random) (mkStdGen 1) :: [Bool]
于 2013-06-25T03:38:33.313 回答