【发布时间】:2016-05-28 09:02:40
【问题描述】:
我正在尝试在 Haskell 中生成一个随机数样本
import System.Random
getSample n = take n $ randoms g where
g = newStdGen
但我似乎并没有以正确的方式使用newStdGen。我错过了什么?
【问题讨论】:
标签: haskell
我正在尝试在 Haskell 中生成一个随机数样本
import System.Random
getSample n = take n $ randoms g where
g = newStdGen
但我似乎并没有以正确的方式使用newStdGen。我错过了什么?
【问题讨论】:
标签: haskell
首先,您可能不想使用newStdGen。最大的问题是每次运行程序时都会得到不同的种子,因此没有可重现的结果。在我看来,mkStdGen 是一个更好的选择,因为它需要你给它一个种子。这意味着您每次都会得到相同的(伪)随机数序列。如果您想要不同的序列,只需更改种子即可。
newStdGen 的第二个问题是,由于它不纯,你最终会进入 IO monad,这可能有点不方便。
sample :: Int -> IO [Int]
sample n = do
gen <- newStdGen
return $ take n $ randoms gen
您可以使用 do-notation 来“提取”这些值,然后对它们求和:
main :: IO ()
main = do
xs <- sample 10
s = sum xs
print s
或者您可以将函数“映射”到结果上(但请注意,在某些时候您可能需要提取值):
main :: IO ()
main = do
s <- fmap sum $ sample 10
print s
fmap 函数是map 的通用版本。就像map 将函数应用于列表中的值一样,fmap 可以将函数应用于IO 中的值。
这个sample 函数的另一个问题是,如果我们再次调用它,它会从一个新的种子开始,而不是继续之前的(伪)随机序列。同样,这使得重现结果变得不可能。为了解决这个问题,我们需要传入种子并返回一个新种子。不幸的是,randoms 不会为我们返回下一个种子,所以我们必须使用 random 从头开始编写。
sample :: Int -> StdGen -> ([Int],StdGen)
sample n seed1 = case n of
0 -> ([],seed1)
k -> let (rs,seed2) = sample (k-1) seed1
(r, seed3) = random seed2
in ((r:rs),seed3)
我们现在的主要功能是
main :: IO ()
main = do
let seed1 = mkStdGen 123456
(xs,seed2) = sample 10 seed1
s = sum xs
(ys,seed3) = sample 10 seed2
t = sum ys
print s
print t
我知道这似乎只是为了使用随机数而做的大量工作,但这样做的好处是值得的。我们可以使用单个种子生成所有随机性,从而保证结果可以重现。
当然,这是 Haskell,我们可以利用 Monads 摆脱种子值的所有手动线程。这是一种稍微高级一点的方法,但非常值得学习,因为 monad 在 Haskell 代码中无处不在。
我们需要这些导入:
import System.Random
import Control.Monad
import Control.Applicative
然后我们将创建一个新类型,它表示将种子转化为值和下一个种子的动作。
newtype Rand a = Rand { runRand :: StdGen -> (a,StdGen) }
我们需要 Functor 和 Applicative 实例,否则 GHC 会报错,但我们可以避免在此示例中实现它们。
instance Functor Rand
instance Applicative Rand
现在是 Monad 实例。这就是魔法发生的地方。 >>= 函数(称为 bind)是我们指定如何通过计算线程化种子值的地方。
instance Monad Rand where
return x = Rand ( \seed -> (x,seed) )
ra >>= f = Rand ( \s1 -> let (a,s2) = runRand ra s1
in runRand (f a) s2 )
newRand :: Rand Int
newRand = Rand ( \seed -> random seed )
现在我们的sample 函数非常简单!我们可以利用Control.Monad 中的replicateM,它重复给定的操作并将结果累积到一个列表中。所有具有种子价值的有趣业务都在幕后处理
sample :: Int -> Rand [Int]
sample n = replicateM n newRand
main :: IO ()
main = do
let seed1 = mkStdGen 124567
(xs,seed2) = runRand (sample 10) seed1
s = sum xs
print s
如果我们需要多次生成随机值,我们甚至可以留在 Rand monad 中。
main :: IO ()
main = do
let seed1 = mkStdGen 124567
(xs,seed2) = flip runRand seed1 $ do
x <- newRand
bs <- sample 5
cs <- sample 10
return $ x : (bs ++ cs)
s = sum xs
print s
我希望这会有所帮助!
【讨论】:
sum $ map someFunction randomNumbers)。但是,上面生成的随机数是一元的(这是一个词吗?),似乎我不能把它们放在我的纯函数中。在 Haskell 中解决此问题的正确方法是什么?
fmap sum $ map someFunction randomNumbers,或使用mkStdGen 123456 代替newStdGen。 (123456 可以是任意整数)