我正在尝试在Haskell中获取一个随机数。 (我目前正在学习并且没有使用Monads或IO等)问题是System.Random中的函数都返回一个IO Int,然后我不能在我的其余代码中使用它Int和Float。
这里的目标是从列表中选择一对,其中第一对是表示概率的浮点数。所以我的计划是使用随机数根据其概率选择一对。
答案 0 :(得分: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
所以你看,你可以在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希望保证纯函数在给定相同输入的情况下始终返回相同的值。
它现在可能很烦人,但它是功能程序员工具包中的强大工具。
答案 1 :(得分:6)
如前所述,随机数不能真正是纯值 1 。
然而,这并不需要打扰你。反过来看一下:其他语言根本就没有纯值这样的东西,它总是处理你正在处理的真实干扰。 Haskell也可以在IO
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等),也不会有效,通过acc'dList
加扰 O ( n )对于每个请求的值 2 。但更重要的是,请注意它是纯函数!尽可能使用纯函数通常是个好主意,只有在绝对必要时才进入IO
。像现在一样:我们需要获得0到1之间的单个Double
值。简单!
main = do
lookupVal <- randomRIO (0, 1)
print $ probsListLookup [(0.1, 1), (0.2, 2), (0.3, 4), (0.4, 5)] lookupVal
1 至少不是像Int
这样的基本类型;你可以实际上对整个概率分布进行“纯计算”。明确地执行此操作非常麻烦,但Haskell允许您使用specific monad s(或in fact comonads)来使其像Haskell IO(或任何不纯的语言)一样简单但没有危险输入/输出。
2 你可以改善这一点,例如与Data.Map
。
答案 2 :(得分:6)
这里有很好的答案,但我觉得一个更完整的答案会非常简单地展示如何在Haskell中获取和使用随机数,这对于命令式程序员来说是有意义的。
首先,您需要一个随机种子:
import System.Random
newRand = randomIO :: IO Int
由于newRand
的类型为IO Int
而非Int
,因此不能将其用作函数参数。 (这将Haskell函数保留为纯函数,它们将始终在同一输入上返回相同的结果。)
然而,我们可以在GHCI中输入newRand
并每次获得一个独特的随机种子。这是可能的,因为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]
答案 3 :(得分:2)
我不认为这些答案是全局的。对于我的模拟,我懒惰地生成随机数,并使用它们严格运行在一个小的(我的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
答案 4 :(得分:1)
如果你想要真正的随机性,你就不会使用IO - 听起来像是拖累,但这个分离是Haskell非常重要的一个方面。但是,您可以通过自己选择“种子”并使用System.Random中返回一对结果和新种子的纯函数(例如“随机”)来获得半伪随机性。