C# 掷骰子概率

3
我想证明掷骰子出现6的概率是1/6,连续两次掷出6的概率是1/36。我编写的程序试图模拟这个过程。
为了更好地解释我的代码,“attempts”是掷骰子的次数。“diceRoll”是我用来模拟掷骰子的变量。“successiveAttempts”是测量是否连续掷出6的变量,如果没有,则重置while循环。我在while循环中重复10000次,以便多次重复掷骰子测试并得到结果的平均值。因为有时会在3次或80次尝试中连续掷出6,所以我想通过重复过程10000次来获取结果的平均值。
“percentage”是attempts除以测试重复的次数,以获取每次掷骰子的平均尝试次数。当我将while循环中的数字更改为1时,它返回我的程序中的数字6,这是正确的。平均需要6次尝试才能掷出6。但是当我将数字更改为2个连续尝试时,返回42,这是不正确的。平均需要36次尝试才能连续掷出两个6。
我无法弄清楚为什么当while循环中的数字为1和2时,程序的运行情况不同。有人能解释一下我错在哪里吗?如果我的代码令人困惑,我很抱歉,我是初学者,非常困惑。如果有人能帮助我,我将非常感激。
public static void Main(string[] args)
{
    Random numGen = new Random();
    int diceRoll = 0;
    int attempts = 0;
    int successiveAttempts = 0;
    int x = 0;

    while (x < 10000)
    {
        successiveAttempts = 0;
        while (successiveAttempts < 2)
        {
            diceRoll = numGen.Next(1,7);
            if (diceRoll == 6)
            {
                successiveAttempts++;
            } 
            else
            {
                successiveAttempts = 0;
            }
            attempts++;
        }
        x++;
    }
    int percentage = attempts/x;

    Console.WriteLine(percentage);
    Console.WriteLine(attempts);
    Console.ReadKey();
}

2
@Dijkgraaf Random.Next(int,int) 使用的是一个不包括上限的范围。Next(1,7) 返回的数字在1到6之间。 - BJ Myers
2
我想说的主要是:你测量的内容以及为什么期望答案是36并不明显。你所测量的不是“n个骰子的任意投掷结果都是六点的概率是多少”。 - Marc Gravell
你不需要嵌套循环,只需使用外部循环。在第一个条件之后添加另一个条件,检查连续尝试是否大于一次(3次相邻的尝试相当于两次相邻的尝试!)。如果是,则将其计为一次。顺便说一下,由.NET生成的伪随机数可能不像掷骰子那样随机。如果明天你还没有得到接受的答案,我会发布一个带有正确代码的答案。(目前正在平板电脑上编写...) - Zohar Peled
2
掷两个骰子出现两个六的概率是1/36。在一个序列中,连续两次掷出6的概率并不是这个值。考虑由三次随机掷骰子组成的长度为3的序列。有216种不同的可能序列,但只有11个包含两个相邻的6(x6666x,其中x = 1..5;第11个是666)。因此,你可以清楚地看到你的方法是有缺陷的。你应该重复10000次掷两个骰子的过程。如果两个都是6,则算作成功。计算成功的次数并除以10000,你应该得到大约0.02777(即1/36)。 - Chris
撇开你在这里完全搞砸了统计数据的事实不谈,还存在代码问题。百分比、尝试次数和x都是整数,而你正在将其中两个相除并赋值给第三个,这样做(1)会给你一个整数,而不是一个分数,(2) 这不是一个百分比。为什么这个与百分比毫无相似之处的东西被称为“百分比”? - Eric Lippert
显示剩余3条评论
3个回答

2

我认为你所测量的不是你认为的。你没有测试分开的试验来观察“掷N个6的机会有多大”。如果要这样做,你需要像下面这样操作:

public static void Main()
{
    Random numGen = new Random();
    int succeses = 0;

    const int TRIES = 10000;
    for(int i = 0; i < TRIES;i++)
    {
        bool allSixes = true;
        const int DICE_PER_TRY =2;
        for(int j = 0; j < DICE_PER_TRY; j++)
        {
            if(numGen.Next(1, 7) == 6)
            {
                // still good
            }
            else
            {
                allSixes = false;
                break; // might a well give up and
                       // **start the next test**
                       // (reset the test)
            }
        }
        if (allSixes) succeses++;
    }
    var triesPerSuccess = TRIES / succeses;

    Console.WriteLine(triesPerSuccess);
    Console.ReadKey();
}

主要的区别在于你正在测量一个连续的投掷系列中连续出现的六个点数。没有理由认为这个答案与1/36有任何关系。


我建议将试验次数增加到一百万次(这仍然非常快),并将“triesPerSuccess”更改为double,以便您可以看到它与36有多接近(因为运行一百万次试验可以让您接近目标数字的一小部分,我认为比较小的试验更令人满意,因为在10000次试验中,转换为int时会抛出32和38等远离目标数字的值)。 - Chris
Marc,你能进一步澄清我在逻辑上哪里出错了吗?我对统计学的理解很差。你是说掷两个六的机会不是1/36吗?我真的很抱歉我的困惑。 - Skullgrabber
@MichaelN。在正好两次掷骰子中获得2个连续的六的机会是36分之1。我的意思是,这不是你正在做的事情。你在接二连三地掷很多骰子,然后数有多少次相邻的六。那是一种完全无关的命题。考虑三个骰子 - 有216种不同的结果;其中11个涉及相邻的6 - 66 [1-5],[1-5] 66 和 666 - 关键是:它与1/36没有任何关系了 - 编辑:lol,这与Chris所说的完全相同 - 我想这是显而易见的下一个例子。 - Marc Gravell
顺便提一下,通过使用内部循环,您不仅可以掷骰子10000次,而且还可以更多。实际上,我在rextester上测试了我的单个循环与您的实现,并发现您平均有11600-11700个骰子投掷。 - Zohar Peled
@Zahar确实如此-这需要进行10000次掷骰子,才能得到6个六。如果你想要10000次掷骰子:把10000除以6(就我个人而言,我仍然会保留“失败后重置”的设置,所以实际上需要的掷骰子数量会少得多)。 - Marc Gravell
我明白了。这也意味着三个连续的骰子点数为6的结果与两个连续的6的结果相同,这可以解释我的实现和你的实现之间的差异 - 我只进行了一次循环,并对每个在另一个6之后出现的6的结果计数成功,这意味着666将计为6个成功的结果。 - Zohar Peled

1

为了给您提供另一种实现方式,请尝试以下代码:

var n = 1000000;
var rnd = new Random();
var trials = Enumerable.Range(0, n).Select(x => new { first = rnd.Next(1, 7), second = rnd.Next(1, 7) }).ToArray();
var pairsOfSixes = trials.Where(x => x.first == 6 && x.second == 6).Count();
var probability = (double)pairsOfSixes / n;

这给了我像 0.02778 这样的值,与期望值 0.0277777777777778 差别不大。

为什么要使用不必要的 ToArray()?您不需要急切地评估可枚举对象并一次性将所有对象存储在内存中以进行计数。此外,Where(x => x.first == 6 && x.second == 6).Count() 可以替换为 Count(x => x.first == 6 && x.second == 6) - Joshua Webb
1
@JoshuaWebb - 我同意这两点。我倾向于使用.ToArray(),因为我预计会多次使用该序列,而且不想被多次打击,特别是当序列包含随机生成的数据时。 - Enigmativity
2
对于非确定性数据,我会尽早调用 ToArray 或类似的方法。否则,你可能会想做类似这样的操作:successes = trials.Where(x=>x.first==x.second); failures = trials.Where(x=>x.first!=x.second);,但这两个结果实际上不会等于你生成的试验次数,因为你刚刚生成了两次数据。延迟计算很好,但有时也容易出现错误。 - Chris

0

嗯,正如Marc、Chris、Eric和其他人所写的那样,连续掷出数字的机会与掷几个骰子并得到相同数字的机会是不同的——因此我的答案可能是多余的,但我在评论中写道,你可以在单个循环中进行计算,如果你在我之前没有接受答案,我会发布一个答案,所以这里是:

public static void Main(string[] args)
{
    Random numGen = new Random();
    int success = 0, successiveAttempts = 0, tries = 10000;
    for( int  x = 0; x < tries; x++)
    {
        successiveAttempts = (numGen.Next(1, 7)== 6) ? successiveAttempts + 1 : 0;
        if(successiveAttempts > 1) 
            success++;
    }
    Console.WriteLine("{0} successive roles of 6 in {1} tries.", success, tries);
    Console.WriteLine("Ratio is {0}", tries/success);
}

请注意,它仍然没有返回您期望的1/36比率。要获得该比率,您应该在每次尝试中掷两个骰子,就像Enigmativity的答案所示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接