【问题标题】:Average of large number of Dice Rolls in HaskellHaskell中大量掷骰子的平均值
【发布时间】:2011-08-22 08:02:40
【问题描述】:

为了更好地学习 Haskell,我正在尝试编写一个程序,显示 2 个骰子的总和的平均值,滚动 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 的两个独立掷骰的总和。

【问题讨论】:

  • 结果是 6,SCNR :)
  • 其实两个骰子和的期望值是(1+2+3+4+5+6)/6 * 2 = 7。这个练习的重点是学习如何用语言编写程序,而不是统计方面。
  • 呵呵 :) 计算一个骰子的期望值后出现舍入误差 :)
  • 在链接的帖子中,解开R [Int]的方法是使用runRandom函数。

标签: haskell random functional-programming


【解决方案1】:

sum 不适合对长列表求和,它在线性空间中运行。试试这个严格版本的sum

sum' = foldl' (+) 0

foldl'Data.List 中定义。

编辑更多信息可以在this HaskellWiki article找到。

【讨论】:

  • 谢谢,我已经很久没有做功能性的东西了。顺便说一句,我想知道如果程序分析得出的计算结果将被评估,是否可以让 sum 从惰性切换到严格。
  • @benson:是的,有严格分析之类的东西,虽然我不确定为什么它在这种情况下不起作用。
  • @n.m. AFAIK 仅在启用优化时才进行严格性分析。使用ghc -O1 或更高版本进行编译。
  • @n.m.默认为无优化。
【解决方案2】:

实际上,这里的概率建模不正确。在编写代码时,得到 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 中随机数生成的知识。

【讨论】:

  • 当然,您可以从一个列表中查看对,而不是获得两个 gens,这可能会稍微干净一些:-)
  • @sclv - 是的。我从 State monad Wikibooks 文章 (en.wikibooks.org/wiki/Haskell/Understanding_monads/State) 上的“clumsyRollDice”函数向后思考,每次滚动后都会创建一个新的 StdGen。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-08
  • 2021-06-18
相关资源
最近更新 更多