为了更好地学习Haskell,我正在尝试编写一个程序,显示2个die的总和的平均值,滚动X次。这在C,Java,Python中相当简单......但我陷入了Haskell。这是一个天真的尝试:
import System.Random
main = do
g <- getStdGen
let trials = 10000000
let rolls = take trials (randomRs (2, 12) g :: [Int])
let average = div (sum rolls) trials
print average
对于少量试验,该程序有效。但是当我用一千万次试验运行这个代码时,我收到一个错误:
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
必须有更好的方法来编写这个程序。在C,Java和Python版本中,这是一项简单的任务。我查看了this帖子(并了解了大约75%的内容),但是当我将代码调整到这种情况时,总结一系列R [Int]
不起作用(我是不知道如何“解开”[Int])。我究竟做错了什么?什么是正确的方法?如何在Haskell中获得随机数启示?
编辑除了选择的答案外,正如rtperson所指出的,2个骰子的建模是不正确的;它应该是从1到6的两个独立卷的总和。
答案 0 :(得分:7)
sum
总结一个长列表并不好,它在线性空间中运行。试试这个严格版本的sum
:
sum' = foldl' (+) 0
foldl'
在Data.List
中定义。
编辑可以在this HaskellWiki article中找到更多信息。
答案 1 :(得分:4)
实际上,这里的概率建模不正确。在编写代码时,获得2到12的可能性相同。但这不是骰子的工作方式。在12种可能的结果中,只有一种方法可以获得2(通过1和1)和12(通过6和6)。但是有6种方法可以得到7(1-6,2-5,3-4,4-3,5-2,6-1)。对两个骰子进行建模,而不是单个2到12个机会,将给出正确的预期值7。
然而 - 这就是你的头发真正开始卷曲的地方 - 你不能简单地做以下事情:
let rolls1 = take trials (randomRs (1, 6) g :: [Int])
let rolls2 = take trials (randomRs (1, 6) g :: [Int])
因为rolls1和rolls2将产生相同的结果。
*Main> let rolls = zip rolls1 rolls2
*Main> take 10 rolls
[(3,3),(4,4),(5,5),(3,3),(5,5),(1,1),(3,3),(1,1),(3,3),(3,3)]
因此,您的结果将始终均匀,因此仍然是错误的答案6。
要获得两个明显随机的列表,您必须生成一个新的StdGen:
import System.Random
import Data.List
main = do
g <- getStdGen
b <- newStdGen -- calls "split" on the global generator
let trials = 10000000
let rolls1 = take trials (randomRs (1, 6) g :: [Int])
let rolls2 = take trials (randomRs (1, 6) b :: [Int])
let rolls = zipWith (+) rolls1 rolls2
let average = div (foldl' (+) 0 rolls) trials
print average
(请注意,我特别避免使用State monad。欢迎你。)
这比原始版本花费的时间更长,但zipWith
的懒惰与foldl'
的严格性相结合,确保您不会溢出堆栈。
如果你只是想解决溢出问题,那么n.m.的回答是正确的。 foldl'
的严格性将解决性能问题。但在这种情况下,获得正确的答案也会教你一些关于Haskell中随机数生成的信息。