斯卡拉Pi的蒙特卡罗计算

Mic*_*ael 10 scala montecarlo

假设我想用蒙特卡罗模拟计算Pi作为练习.

我正在编写一个函数,它(0, 1), (1, 0)随机选取一个正方形中的一个点并测试该点是否在圆内.

import scala.math._
import scala.util.Random

def circleTest() = {
  val (x, y) = (Random.nextDouble, Random.nextDouble)
  sqrt(x*x + y*y) <= 1
}
Run Code Online (Sandbox Code Playgroud)

然后我正在编写一个函数,它将测试函数和试验次数作为参数,并返回测试结果为真的试验部分.

def monteCarlo(trials: Int, test: () => Boolean) =
  (1 to trials).map(_ => if (test()) 1 else 0).sum * 1.0 / trials
Run Code Online (Sandbox Code Playgroud)

......我可以计算Pi

monteCarlo(100000, circleTest) * 4
Run Code Online (Sandbox Code Playgroud)

现在我想知道monteCarlo功能是否可以改进.你怎么写monteCarlo高效和可读?

例如,由于试验的数量很大,是否值得使用viewiterator代替Range(1, trials)reduce代替mapsum

Tra*_*own 8

值得注意的Random.nextDouble是副作用 - 当你调用它时会改变随机数生成器的状态.这对你来说可能不是一个问题,但由于这里已有五个答案,我认为添加一个纯功能的东西不会有任何损害.

首先,您需要一个随机数生成monad实现.幸运的是,NICTA提供了一个与Scalaz集成的非常好的.你可以像这样使用它:

import com.nicta.rng._, scalaz._, Scalaz._

val pointInUnitSquare = Rng.choosedouble(0.0, 1.0) zip Rng.choosedouble(0.0, 1.0)

val insideCircle = pointInUnitSquare.map { case (x, y) => x * x + y * y <= 1 }

def mcPi(trials: Int): Rng[Double] =
  EphemeralStream.range(0, trials).foldLeftM(0) {
    case (acc, _) => insideCircle.map(_.fold(1, 0) + acc)
  }.map(_ / trials.toDouble * 4)
Run Code Online (Sandbox Code Playgroud)

然后:

scala> val choosePi = mcPi(10000000)
choosePi: com.nicta.rng.Rng[Double] = com.nicta.rng.Rng$$anon$3@16dd554f
Run Code Online (Sandbox Code Playgroud)

什么都没有计算好 - 我们刚刚建立了一个计算,它会在执行时随机生成我们的值.IO为方便起见,我们现在就在monad中执行它:

scala> choosePi.run.unsafePerformIO
res0: Double = 3.1415628
Run Code Online (Sandbox Code Playgroud)

这不是最高性能的解决方案,但它足够好,对许多应用程序来说可能不是问题,参考透明度可能是值得的.


The*_*aul 3

基于流的版本,另一种选择。我想这已经很清楚了。

def monteCarlo(trials: Int, test: () => Boolean) =
    Stream
      .continually(if (test()) 1.0 else 0.0)
      .take(trials)
      .sum / trials
Run Code Online (Sandbox Code Playgroud)

(它sum不是专门用于流的,但实现(在 TraversableOnce 中)只是调用foldLeft专门的并且“允许 GC 沿途收集。”因此 .sum 不会强制对流进行评估,因此不会保留记忆中的所有试验一次)