13

我正在使用我的应用程序中的类型类来生成随机数System.RandomRandom但是我想用一个函数生成一个任意长度的随机浮点列表randoms :: StdGen -> Int -> ([Float], StdGen)

没有获得新生成器的限制,我可以轻松编写 randoms gen n = (take n $ randoms gen) :: [Float]

然而,这给我留下了与我开始时相同的随机生成器,这意味着如果我要连续两次运行这个函数,我会得到相同的列表,除非我去其他地方使用生成器来获得一个新的。

如何生成无限(或任意长度)的随机值列表,同时“刷新”我的随机生成器。

4

4 回答 4

30

好吧,让我们看看您拥有的功能:

random :: StdGen -> (Float, StdGen)  -- From System.Random

我们可以将其包装在Statemonad 中以获得有状态的计算:

state :: (s -> (a, s)) -> State s a  -- From Control.Monad.Trans.State

random' :: State StdGen Float
random' = state random

现在,我们可以使用以下方法生成一堆浮点数replicateM

replicateM :: (Monad m) => Int -> m a -> m [a]  -- From Control.Monad

randoms' :: Int -> State StdGen [Float]
randoms' n = replicateM n random'

最后,我们解包State以取回显式生成器传递:

randoms :: Int -> StdGen -> ([Float], StdGen)
randoms n = runState (randoms' n)

如果将所有这些组合到一个函数定义中,您将得到:

randoms :: Int -> StdGen -> ([Float], StdGen)
randoms n = runState (replicateM n (state random))

换句话说,我们可以将这个过程描述为:

  • 包裹randomState单子中
  • 复制它n的时间
  • 打开它

这就是为什么单子是一个如此重要的概念。从 monad 接口的角度来看,起初看起来很棘手的事情往往是简单的计算。

于 2013-01-07T20:46:42.397 回答
5

Gabriel 的回答是正确的,这几乎就是 MonadRandom 包的实现方式(使用随机生成器参数化的状态 Monad)。

它可以节省您每次定义它的时间,并且它还带有一个 Monad 转换器,因此您可以将任何其他 Monad 转换为也可以产生随机值的 Monad。

您的示例可以很容易地实现为:

(runRand $ take n `fmap` getRandoms) :: RandomGen g => g -> ([Int], g)

StdGen 恰好是 RandomGen 的一个实例,因此您只需将其插入即可!

于 2013-01-07T21:29:14.840 回答
3

An alternative without State or split, using mapAccumL from Data.List (and swap from Data.Tuple):

nRandoms n gen =  mapAccumL(\g _ -> swap $ random g) gen [1..n]

though I have to say I don't have a convincing argument for why this should be better in any way.

于 2013-01-07T21:28:10.780 回答
1

您可以定义一个函数,其类型与您想要的类型相匹配,尽管更普遍。

import System.Random

randoms' :: (RandomGen g, Random a) => g -> Int -> ([a], g)
randoms' g n =
  let (g1, g2) = split g
  in (take n $ randoms g1, g2)

尽管它使用split

split :: g -> (g, g)

split操作允许获得两个不同的随机数生成器。这在函数式程序中非常有用(例如,在将随机数生成器向下传递给递归调用时),但在统计上稳健的实现方面所做的工作却很少split……</p>

它仍然没有做你想做的事。(我Bool在下面的示例中使用以便更容易进行视觉比较。)

ghci> g <- getStdGen
ghci> randoms' g 5 :: ([Bool], StdGen)
([False,False,False,True,False],1648254783 2147483398)
ghci> randoms' g 5 :: ([Bool], StdGen)
([False,False,False,True,False],1648254783 2147483398)

请注意,随机数组是相同的。

尽管该函数很麻烦地拆分了生成器,但我们还是立即将其丢弃。相反,g2通过将其线程化到后续调用来使用,如

ghci> let (a1,g2) = randoms' g 5 :: ([Bool], StdGen)
ghci> let (a2,_) = randoms' g2 5 :: ([Bool], StdGen)
ghci> (a1,a2)
([False,False,False,True,False],[True,True,True,False,True]

如果您的代码在IOmonad 中运行,则可以setStdGen在末尾使用替换全局随机数生成器,如

myAction :: Int -> IO ([Float],[Float])
myAction n = do
  g <- getStdGen
  let (f1,g2) = randoms' g n
  let (f2,g3) = randoms' g2 n
  setStdGen g3
  return (f1, f2)

线程状态是尴尬且容易出错的。考虑使用State或者ST如果你有很多重复的样板。

于 2013-01-07T21:33:43.733 回答