Haskell中大量骰子卷的平均值

时间:2011-08-22 08:02:41

标签: haskell random functional-programming

为了更好地学习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的两个独立卷的总和。

2 个答案:

答案 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中随机数生成的信息。