随着迭代次数的增加,使用 System.Random 的代码输出没有达到理论极限

Output of code using System.Random does not approach theoretical limit as iterations increase

我不擅长统计,所以我尝试用 C# 解决一个简单的问题。问题:"A given team has a 65% chance to win a single game against another team. What is the probability that they will win a best-of-5 set?"

我想看看该概率与该组游戏数量之间的关系。 Bo3 与 Bo5 等相比如何?

我通过创建 SetGame 对象以及 运行 迭代来做到这一点。使用此代码完成获胜决定:

Won = rnd.Next(1, 100) <= winChance;
正如您所料,

rnd 是一个静态 System.Random 对象。

这是我的 Set 目标代码:

public class Set
{
    public int NumberOfGames { get; private set; }
    public List<Game> Games { get; private set; }
    public Set(int numberOfGames, int winChancePct)
    {
        NumberOfGames = numberOfGames;
        GamesNeededToWin = Convert.ToInt32(Math.Ceiling(NumberOfGames / 2m));
        Games = Enumerable.Range(1, numberOfGames)
                          .Select(i => new Game(winChancePct))
                          .ToList();
    }

    public int GamesNeededToWin { get; private set; }
    public bool WonSet => Games.Count(g => g.Won) >= GamesNeededToWin;
}

我的问题是我得到的结果不尽如人意。不擅长统计的人帮我算了算,看来我的代码总是高估了赢盘的几率,而且迭代次数并没有提高准确性。

我得到的结果(每局比赛获胜的百分比)如下。第一列是每局的比赛,接下来是统计胜率(我的结果应该接近),剩下的列是我基于迭代次数的结果。如您所见,更多的迭代似乎并没有使数字更准确。

每盘比赛|预期盘胜率|10K|100K|1M|10M

1 65.0% 66.0% 65.6% 65.7% 65.7%

3 71.8% 72.5% 72.7% 72.7% 72.7%

5 76.5% 78.6% 77.4% 77.5% 77.5%

7 80.0% 80.7% 81.2% 81.0% 81.1%

9 82.8% 84.1% 83.9% 83.9% 83.9%

整个项目都发布在 github here 上,如果你想看的话。

如果能深入了解为什么这不能产生准确的结果,我们将不胜感激。

快速查看,Random 函数的上限是唯一的,因此需要设置为 101

Darren Sisson 的回答是正确的;您的计算偏差了大约 1%,因此您的所有结果也是如此。

我的建议是通过将所需的语义封装到一个对象中来解决问题,然后您可以独立测试该对象:

interface IDistribution<T>
{
  T Sample();
}
static class Extensions 
{
  public static IEnumerable<T> Samples(this IDistribution<T> d)
  {
    while (true) yield return d.Sample();
  }
}
class Bernoulli : IDistribution<bool>
{
  // Note that we could also make it IDistribution<int> and return
  // 0 and 1 instead of false and true; that would be the more 
  // "classic" approach to a Bernoulli distribution.  Your choice.
  private double d;
  private Random random = new Random();
  private Bernoulli(double d) { this.d = d; }
  public static Make(double d) => new Bernoulli(d);
  public bool Sample() => random.NextDouble() < d;
}

现在你有了一个可以独立测试的有偏见的投币器。您现在可以编写如下代码:

int flips = 1000;
int heads = Bernoulli
  .Make(0.65)
  .Samples()
  .Take(flips)
  .Where(x => x)
  .Count();

抛 1000 次硬币,正面朝上的概率为 65%。

请注意,我们在这里所做的是构造一个概率分布monad,然后使用LINQ 工具来表达条件概率。这是一项强大的技术;您的应用程序仅仅触及了我们可以用它做什么的皮毛。

练习:构造扩展方法 WhereSelectSelectMany,它们不是 IEnumerable<T>,而是 IDistribution<T>;你能根据分布类型本身来表达分布的语义,而不是从分布 monad 到序列 monad 的转换吗?您可以对 zip 连接执行相同的操作吗?

练习:构建 IDistribution<T> 的其他实现。你能构建双打的柯西分布吗?正态分布呢? n 面的公平骰子上的骰子滚动分布怎么样?现在,你能把这些放在一起吗?什么是分布:抛硬币;如果正面朝上,掷四个骰子并将它们加在一起,否则掷两个骰子并丢弃所有双打,然后将结果相乘。