Haskell中的随机数

Jon*_*an. 22 random haskell

我想在Haskell中获取一个随机数.(我目前正在学习并且还没有使用Monads或IO等)问题是System.Random中的函数都返回一个IO Int,然后我不能在我的其余代码中使用它Int和Float.

这里的目标是从列表中选择一对,其中该对中的第一个是表示概率的浮点数.所以我的计划是使用随机数根据其概率选择一对.

bhe*_*ilr 27

这是新Haskell程序员的常见障碍.你逃避IO,需要一段时间来找出最好的方法.Learn You A Haskell教程很好地解释了使用状态monad生成随机数的一种方法,但它仍然需要使用getStdGen或者newStdGen在IO中进行种子播种.

对于简单的情况,你可以做类似的事情

myPureFunction :: Float -> Float
myPureFunction x = 2 * x

main :: IO ()
main = do
    -- num :: Float
    num <- randomIO :: IO Float
    -- This "extracts" the float from IO Float and binds it to the name num
    print $ myPureFunction num
Run Code Online (Sandbox Code Playgroud)

所以你看,你可以得到你的随机数main,然后将该值传递给执行处理的纯函数.

您可能会问自己为什么在Haskell中生成随机数的所有工作.有很多好的理由,其中大部分都与类型系统有关.由于生成随机数需要修改操作系统中StdGen的状态,所以它必须存在于内部IO,否则你可以拥有一个纯函数,每次都会给你不同的结果.

想象一下这种人为的情景:

myConstant :: Int
myConstant = unsafePerformIO randomIO

blowUpTheWorld :: IO ()
blowUpTheWorld = error "Firing all the nukes"

main :: IO ()
main = do
    if even myConstant
        then print "myConstant is even"
        else blowUpTheWorld
Run Code Online (Sandbox Code Playgroud)

如果你跑了几次,很可能你最终会"解雇所有的核武器".显然,这很糟糕. myConstant应该是,嗯,不变,但每次运行程序时,你都会获得不同的值.Haskell希望保证纯函数在给定相同输入的情况下始终返回相同的值.

它现在可能很烦人,但它是功能程序员工具包中的强大工具.


Mat*_*vis 9

这里有很好的答案,但我觉得一个更完整的答案会非常简单地展示如何在Haskell中获取和使用随机数,这种方式对于命令式程序员来说是有意义的.

首先,你需要一个随机的种子:

import System.Random
newRand = randomIO :: IO Int
Run Code Online (Sandbox Code Playgroud)

因为newRand是类型IO Int而不是Int,它不能用作函数参数.(这将Haskell函数保留为纯函数,它们将始终在同一输入上返回相同的结果.)

但是,我们可以简单地输入newRandGHCI并每次获得一个独特的随机种子.这是可能的,因为newRand它是类型IO而不是标准(不可变)变量或函数.

*Main> newRand
-958036805781772734
Run Code Online (Sandbox Code Playgroud)

然后,我们可以将此种子值复制并粘贴到为我们创建随机数列表的函数中.如果我们定义以下函数:

randomList :: Int -> [Double]
randomList seed = randoms (mkStdGen seed) :: [Double]
Run Code Online (Sandbox Code Playgroud)

当函数在GHCI中运行时粘贴到给定的种子中:

*Main> take 10 randomList (-958036805781772734)
[0.3173710114340238,0.9038063995872138,0.26811089937893495,0.2091390866782773,0.6351036926797997,0.7343088946561198,0.7964520135357062,0.7536521528870826,0.4695927477527754,0.2940288797844678]
Run Code Online (Sandbox Code Playgroud)

请注意我们如何从0到1获得熟悉的值(不包括).我们不像在命令式语言中那样每次迭代生成一个新的随机数,而是提前生成一个随机数列表,并在每次连续递归时使用列表尾部的头部.一个例子:

pythagCheck :: [Double] -> [Double] -> [Int]
pythagCheck (x:xs) (y:ys)
   | (a^2) + (b^2) == (c^2) = [a, b, c]
   | otherwise              = pythagCheck xs ys
  where aplusb = ceiling (x * 666)
        a = ceiling (y * (fromIntegral (aplusb - 1)))
        b = aplusb - a
        c = 1000 - a - b
Run Code Online (Sandbox Code Playgroud)

提前创建两个列表并将它们作为参数输入允许我们搜索(唯一的!)毕达哥拉斯三元组,其中a + b + c = 1000.当然,你会想要为每个列表使用不同的随机种子列表:

*Main> newRand
3869386208656114178
*Main> newRand
-5497233178519884041
*Main> list1 = randomList 3869386208656114178
*Main> list2 = randomList (-5497233178519884041)
*Main> pythagCheck list1 list2
[200,375,425]
Run Code Online (Sandbox Code Playgroud)


lef*_*out 6

如前所述,随机数不能真正是纯值1.

但是,这并不需要打扰你.反过来看一下:其他语言根本就没有纯值这样的东西,它总是处理你正在处理的真实干扰.Haskell也可以在IOmonad中做到这一点.你不需要知道它是如何工作的,只是模仿它在程序语言中的样子(尽管这里有一些陷阱).

首先,你需要一些算法,这与语言没有任何关系.显而易见的方法是在列表中累积概率,并使用生成的步进函数作为[0,1] [到您想要的值]的映射.

probsListLookup :: [(Double, a)] -> Double -> a
probsListLookup pAssoc = look acc'dList
 where acc'dList = scanl1 (\(pa,_) (pn,x) -> (pa+pn,x)) pAssoc
       look ((pa, x) : pas) rval
         | rval < pa   = look pas rval
         | otherwise   = x
Run Code Online (Sandbox Code Playgroud)

注意,这既不能很好地处理无效输入(概率不能总和为1等),也不是有效的,为每个请求的值2加扰O(n).更重要的是,请注意它是一个纯粹的功能!尽可能使用纯函数通常是一个好主意,只有在绝对必要时才进行.像现在一样:我们需要获得0到1之间的单个值.简单!acc'dListIODouble

main = do
   lookupVal <- randomRIO (0, 1)
   print $ probsListLookup [(0.1, 1), (0.2, 2), (0.3, 4), (0.4, 5)] lookupVal
Run Code Online (Sandbox Code Playgroud)

1至少不是基本类型Int; 实际上你可以对整个概率分布进行"纯计算" .明确地执行此操作非常麻烦,但Haskell允许您使用特定的monad(或实际上是comonads)来使其像Haskell IO(或任何不纯的语言)一样简单,但没有输入/输出的危险.

2你可以改善它,例如Data.Map.


Nar*_*ius 1

如果你想要真正的随机性,你将无法回避使用 IO - 听起来像是一种拖累,但这种分离是 Haskell 的一个非常重要的方面。但是,您可以通过自己选择“种子”并使用 System.Random 中返回一对结果和新种子(例如“随机”)的纯函数来获得半伪随机性。