【问题标题】:Generating new and different random lists in Haskell (without IO)?在 Haskell 中生成新的和不同的随机列表(没有 IO)?
【发布时间】:2018-01-07 21:49:19
【问题描述】:

这是一个非常新手的问题,虽然我可以找到部分答案,但我仍然很难让整个事情发挥作用。我在一个模块中有一组函数,可以处理我设计的特定数据类型。有时我需要创建一个随机值列表。我们可以将我的类型视为多项式,以它们的系数列表的形式给出。所以我应该只需要调用一个randomPoly 函数(比如说)在每次调用它时生成新列表。为简单起见,所有列表都可以是相同的长度(比如 4)和相同大小的元素 - 比如在 0 到 9 之间。

所以我想要的是能够做这样的事情(在 ghci 中):

>>> setRandomSeed 42
>>> randomPoly
[6,5,0,4]
>>> randomPoly
[9,6,3,5]   
 >>> randomPoly
[7,3,9,2]

我当然可以在每次需要新列表时通过简单地将新种子传递给生成器来获得不同的随机列表:

>>> randomPoly st = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

但我不想这样做:我想设置一次初始种子,然后让 State 负责管理生成器的当前值。

在这个阶段,我对 monad、State 和所有其他东西的本质不太感兴趣 - 我正在寻找尽可能接近“现成”的解决方案。我只是想要一些我可以使用的东西。大多数示例似乎都非常热衷于教授所有有关 State monad 的工作原理 - 这是一个非常光荣的概念 - 但现在我想要的只是一些快速简单的方法,以便在我需要它们时创建随机列表。

例如,这是一个愚蠢的函数,它创建随机列表直到所有值的总和为偶数:

mkEvenPoly :: Int -> [Integer]
mkEvenPoly st
  | even $ sum p = p
  | otherwise    = mkEvenPoly $ s+1
  where
    p = map (`mod` 10) $ take 4 $ randoms (mkStdGen st) :: [Integer]

请注意,每次我需要将新的生成值传递给mkStdGen。我可以用 State monad 做这种事情吗 - 存储生成器的当前值(由 mkStdGen 返回),然后将其用于下一次随机调用?

【问题讨论】:

  • 您通过传递StdGen 作为状态来使用random 库(例如,将其传递给random,然后继续从调用返回的StdGen 进行下一个伪随机值等)。所以你应该给mkStdGen打一次电话。我首先将mkEvenPoly 重写为StdGen -> [Integer] 类型,暂时忽略State monad
  • 关于如何使用State 来抽象StdGen 的“有状态”传递,我认为你应该从网上的许多小例子之一开始,逐步适应你的功能,或者只是接受您可能需要花一些时间来学习它们。
  • MonadRandom 提供了一个相当不错的界面,我认为可能还有其他类似的。
  • 谢谢各位。是的,当然——我正在花时间学习这个——但这需要相当多的时间!这也有点令人沮丧:每次我认为我有一个概念被舔过时,都会有一些小细节需要在挖掘细节方面进行另一次投资。呃,好吧。正如他们所说,没有王道......

标签: haskell random state-monad


【解决方案1】:

这是一个使用MonadRandom 的“现成”解决方案,与您(我认为)的想法非常接近。

我重写了您的 randomPolymkEvenPoly 函数以返回 Rand monad。因此,它们可以在 do 语句中组合成复合 Rand monad 。在生成这个复合 monad 的 do 循环中,随机变量是在没有显式使用生成器的情况下设置的。将单个生成器与复合 monad 一起传递给 runRand 以生成多个随机值。

import Control.Monad.Random

randomPoly :: Rand StdGen [Integer]
randomPoly = map (`mod` 10) <$> take 4 <$> getRandoms

mkEvenPoly :: Rand StdGen [Integer]
mkEvenPoly = do
  p <- randomPoly
  let res
        | even $ sum p = return p
        | otherwise = mkEvenPoly
  res

-- Use do notation to compose multiple monads into one.
randomStuff :: Rand StdGen (Float, [[Integer]])
randomStuff = do
  f <- getRandom
  p0 <- randomPoly
  p1 <- randomPoly
  e0 <- mkEvenPoly
  e1 <- mkEvenPoly
  e2 <- mkEvenPoly
  return (f, [p0, p1, e0, e1, e2])

main = do
  -- set an initial seed once.
  g0 <- getStdGen 

  -- actually generate the random values.
  let (stuff, g1) = runRand randomStuff g0  

  print stuff

【讨论】:

  • 这太棒了 - 非常感谢!优雅的代码不仅回答了我的问题,还为我提供了一个可供学习的示例。这样的例子在 Haskell 世界中并不容易找到,至少在包和模块描述以及来自 hackage 的代码中是这样。再次感谢您 - 这超出了我的预期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-21
  • 1970-01-01
  • 1970-01-01
  • 2012-02-03
  • 2015-01-15
相关资源
最近更新 更多