32

我试图在 Haskell 中获得一个随机数。(我目前正在学习,还没有进入 Monads 或 IO 等)问题是 System.Random 中的函数都返回一个 IO Int,然后我不能在我使用的其余代码中使用它整数和浮点数。

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

4

5 回答 5

40

这是新 Haskell 程序员的常见障碍。你逃避 IO,需要一段时间才能找到最好的方法。Learn You A Haskell 教程很好地解释了使用 state monad 生成随机数的一种方法,但它仍然必须使用getStdGenor来播种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

所以你看,你可以得到你的随机数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

如果你跑了几次,很可能你最终会“发射所有的核武器”。显然,这很糟糕。 myConstant应该是恒定的,但是每次运行程序时都会得到不同的值。Haskell 希望保证纯函数在给定相同输入的情况下总是返回相同的值。

现在可能很烦人,但它是函数式程序员工具包中的一个强大工具。

于 2013-10-25T16:27:23.197 回答
13

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

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

import System.Random
newRand = randomIO :: IO Int

因为newRandis 的 typeIO Int而不是 not Int,所以它不能用作函数参数。(这将 Haskell 函数保留为纯函数,在相同的输入上总是返回相同的结果。)

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

*Main> newRand
-958036805781772734

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

randomList :: Int -> [Double]
randomList seed = randoms (mkStdGen seed) :: [Double]

当函数在 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]

注意我们如何从 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

提前创建两个列表并将它们作为参数输入,这样我们就可以搜索(一个也是唯一的!)毕达哥拉斯三元组,其中 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]
于 2016-08-24T00:38:07.457 回答
6

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

但是,这并不需要打扰您。换个角度看:其他语言根本没有纯值之类的东西,它总是带有你正在处理的现实世界干扰的状态。IOHaskell 也可以在monad中做到这一点。你不需要知道它是如何工作的,只需模仿它在程序语言中的样子(不过这里有一些陷阱)。

首先你需要一些算法,它与语言没有任何关系。显而易见的方法是在列表中累积概率,并将生成的阶跃函数用作从 [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

请注意,这既不能很好地处理无效输入(概率总和不等于 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

1至少不是像Int; 不过,您实际上可以对整个概率分布进行“纯计算” 。明确地这样做非常麻烦,但是 Haskell 允许您使用特定的 monad s(或实际上是 comonads)使其与在 Haskell IO(或任何不纯语言)中一样简单,但没有输入/输出的危险。

2您可以使用Data.Map.

于 2013-10-25T17:14:13.170 回答
3

我不认为这些答案是全部。对于我的模拟,我懒惰地生成随机数,并在很小的空间(我的 macbook 上为 1.1M)中严格运行它们。

也许随机数只能存在于 IO monad 中的评论指的是真正的随机数,但对于伪随机数来说并非如此,通常人们希望能够重现结果。这是一个例子:

module Main (
  main
  ) where

import qualified Data.Vector.Unboxed as V
import Data.Random.Source.PureMT
import Data.Random
import Control.Monad.State

nItt :: Int
nItt = 1000000000

gridSize :: Int
gridSize = 10

testData :: Int -> V.Vector Double
testData m =
  V.fromList $
  evalState (replicateM m (sample (uniform (0 :: Double) 1.0)))
  (pureMT 2)

test = V.foldl (+) 0 (testData nItt)

main = putStrLn $ show test
于 2014-03-05T07:56:46.680 回答
1

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

于 2013-10-25T16:22:36.270 回答