繁体   English   中英

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

[英]Trying to figure out `random` function in Haskell

我刚学会了random函数。

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

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

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

然后我发现了更多的True不是False的小数字。 我很好奇,并检查如下

> length $ takeWhile randomBool [1..]
53667

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

非正式地,当您使用接近在一起的种子调用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的这一部分可能是一本有用的读物​​。

计算机是确定性的,不能生成随机数。 相反,它们依赖于返回数字分布的数学公式,这些数字看起来是随机的。 这些被称为伪随机数生成器。 但是,由于确定性,我们遇到的问题是,如果我们在每次调用程序时以相同的方式运行这些公式,我们将获得相同的随机数生成器。 显然,这不好,因为我们希望我们的数字是随机的! 因此,我们必须为随机生成器提供从一次运行到另一次运行的初始种子值。 对于大多数人(即那些不做密码填充的人),随机数生成器按当前时间播种。 在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] 要使用x的起始种子生成无限的Bool列表,只需运行randoms (mkStdGen x) 您可以使用length $ takeWhile id (randoms (mkStdGen x))实现您的示例。 您应该确认您得到的不同的初始值不同的值x ,但始终不变的值,如果你提供同样的x

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

希望这可以帮助 :)

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

例如,此代码生成10个Bool

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM