随着迭代次数的增加,使用 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 等相比如何?
我通过创建 Set
和 Game
对象以及 运行 迭代来做到这一点。使用此代码完成获胜决定:
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 工具来表达条件概率。这是一项强大的技术;您的应用程序仅仅触及了我们可以用它做什么的皮毛。
练习:构造扩展方法 Where
、Select
和 SelectMany
,它们不是 IEnumerable<T>
,而是 IDistribution<T>
;你能根据分布类型本身来表达分布的语义,而不是从分布 monad 到序列 monad 的转换吗?您可以对 zip 连接执行相同的操作吗?
练习:构建 IDistribution<T>
的其他实现。你能构建双打的柯西分布吗?正态分布呢? n 面的公平骰子上的骰子滚动分布怎么样?现在,你能把这些放在一起吗?什么是分布:抛硬币;如果正面朝上,掷四个骰子并将它们加在一起,否则掷两个骰子并丢弃所有双打,然后将结果相乘。
我不擅长统计,所以我尝试用 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 等相比如何?
我通过创建 Set
和 Game
对象以及 运行 迭代来做到这一点。使用此代码完成获胜决定:
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 工具来表达条件概率。这是一项强大的技术;您的应用程序仅仅触及了我们可以用它做什么的皮毛。
练习:构造扩展方法 Where
、Select
和 SelectMany
,它们不是 IEnumerable<T>
,而是 IDistribution<T>
;你能根据分布类型本身来表达分布的语义,而不是从分布 monad 到序列 monad 的转换吗?您可以对 zip 连接执行相同的操作吗?
练习:构建 IDistribution<T>
的其他实现。你能构建双打的柯西分布吗?正态分布呢? n 面的公平骰子上的骰子滚动分布怎么样?现在,你能把这些放在一起吗?什么是分布:抛硬币;如果正面朝上,掷四个骰子并将它们加在一起,否则掷两个骰子并丢弃所有双打,然后将结果相乘。