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

时间:2017-11-06 18:14:46

标签: c# random probability

我不擅长统计数据,所以我试图在C#中解决一个简单的问题。问题是:“特定球队有65%的机会赢得一场对阵另一支球队的比赛。他们赢得最佳5场比赛的几率是多少?”

我想看看这个概率与集合中游戏数量之间的关系。 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;
}

我的问题是我得到的结果不是他们应该的。对于我来说,数据较少的人会为我做数学计算,而且我的代码似乎总是过高估计赢得该集合的机会,并且迭代次数并不会提高准确性。

我得到的结果(%set by game per set)低于。第一列是每套游戏,第二列是统计赢率(我的结果应该接近),其余列是基于迭代次数的结果。正如您所看到的,更多迭代似乎并没有使数字更准确。

每套游戏|预期套装赢率| 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上。

非常感谢任何有关为什么这不会产生准确结果的见解。

2 个答案:

答案 0 :(得分:6)

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的工具来表达条件概率。这是一项强大的技术;你的应用程序几乎没有触及我们用它做什么的表面。

练习:构建不是Where而是Select的扩展方法SelectManyIEnumerable<T>IDistribution<T>;你能用分布类型本身来表达分布的语义,而不是从分布monad到序列monad进行转换吗?你能为zip连接做同样的事吗?

练习:构建IDistribution<T>的其他实现。你能构建一个柯西分布的双打吗?那么正态分布怎么样?在n侧的公平模具上进行骰子滚动分布怎么样?现在,你能把这些全部放在一起吗?分配是什么:翻转硬币;如果是头,掷四个骰子并将它们加在一起,否则掷出两个骰子并丢弃所有双打,并将结果相乘。

答案 1 :(得分:1)

快速查看,随机函数的上限是独占的,因此需要设置为101