我想在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希望保证纯函数在给定相同输入的情况下始终返回相同的值.
它现在可能很烦人,但它是功能程序员工具包中的强大工具.
这里有很好的答案,但我觉得一个更完整的答案会非常简单地展示如何在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)
如前所述,随机数不能真正是纯值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.
如果你想要真正的随机性,你将无法回避使用 IO - 听起来像是一种拖累,但这种分离是 Haskell 的一个非常重要的方面。但是,您可以通过自己选择“种子”并使用 System.Random 中返回一对结果和新种子(例如“随机”)的纯函数来获得半伪随机性。
| 归档时间: |
|
| 查看次数: |
19745 次 |
| 最近记录: |