首页 » 与孩子一起学编程 » 与孩子一起学编程全文在线阅读

《与孩子一起学编程》23.2 掷骰子

关灯直达底部

几乎所有人都玩过用到骰子的游戏,可能是 Monopoly、Yahtzee、Trouble、 Backgammon 或者别的游戏。不论是哪个游戏,掷骰子都是在游戏中生成随机事件的最常用的方式之一。

骰子在程序中很容易模拟,Python 的 random 模块提供了两种方法来完成这项工作。一种方法是使用 randint 函数,它会选择一个随机的整数。由于骰子各面上的点数都是整数(1、2、3、4、5 和 6),所以可以这样模拟掷骰子:

import randomdie_1 = random.randint(1, 6)

这会给出介于 1 到 6 之间的一个数,每个数出现的几率相等。这就像是一个真正的骰子。

要完成同样的工作还有一种方法,可以建立所有可能结果的一个列表,然后使用 choice 函数从这个列表中选择一个结果。具体做法如下:

import randomsides = [1, 2, 3, 4, 5, 6]die_1 = random.choice(sides)

这与前一个例子的原理完全相同。choice 函数随机地从列表中选择一项。在这里,列表中包含从 1 到 6 的数字。

多个骰子

如果想模拟掷两个骰子呢?如果你只是想把两个骰子的结果相加来得到总数,可能会考虑这样做:

two_dice = random.randint(2, 12)

毕竟,两个骰子的总和可以是 2 到 12,对不对?嗯,也对也不对。你确实会得到一个介于 2 到 12 之间的随机数,但是不能只是将两个从 1 到 6 的随机数相加来得到。这行代码所做的就像是掷一个有 11 面的大骰子,而不是两个 6 面的骰子。不过这有什么区别呢?这就引入一个主题:概率。要了解二者的差别,最简单的方法就是试一试。

下面我们将掷多次骰子,并跟踪每个面总共出现多少次。这里利用一个循环和一个列表来实现。循环用来掷骰子,列表跟踪每个面出现的次数。下面先来看 11 个面的骰子,如代码清单 23-1 所示。

代码清单 23-1 将一个 11 面的骰子掷 1000 次

列表的索引是从 0 到 12,不过前两个不会使用,因为我们不关心总数 0 和 1,这是不可能发生的 。得到一个结果时,我们将相应的列表项增 1 C。如果结果为 7,就将 totals[7] 增 1 。所以 totals[2] 就是得到 2 的次数,totals[3] 是得到 3 的次数。依此类推。

如果运行这个代码,会得到这样的结果:

total 2 came up 95 timestotal 3 came up 81 timestotal 4 came up 85 timestotal 5 came up 86 timestotal 6 came up 100 timestotal 7 came up 85 timestotal 8 came up 94 timestotal 9 came up 98 timestotal 10 came up 93 timestotal 11 came up 84 timestotal 12 came up 99 times

如果查看总数,可以看到所有数出现的次数大致相同,都介于 80 到 100 之间。它们出现的次数并不完全一样,因为这些数都是随机的。不过它们都很接近,而且哪些数出现次数更多并没有明显的规律。你可以运行这个程序,多试几次,确认这一点。或者你也可以把循环次数增加到 10 000 或 100 000 试试看。

现在用两个 6 面的骰子做同样的事情。代码清单 23-2 中的代码会完成这项工作。

代码清单 23-2 将两个 6 面的骰子掷 1 000 次
import randomtotals = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]for i in range(1000):    die_1 = random.randint(1, 6)    die_2 = random.randint(1, 6)    dice_total = die_1 + die_2    totals[dice_total] += 1for i in range (2, 13):    print /"total/", i, /"came up/", totals[i], /"times/"

运行这个程序,会得到类似下面的输出:

total 2 came up 22 timestotal 3 came up 61 timestotal 4 came up 93 timestotal 5 came up 111 timestotal 6 came up 141 timestotal 7 came up 163 timestotal 8 came up 134 timestotal 9 came up 117 timestotal 10 came up 74 timestotal 11 came up 62 timestotal 12 came up 22 times

可以注意到,最大的数和最小的数出现比较少,而中间的数字(如 6 和 7)出现得更频繁一些。这与只有一个 11 面 骰子的情况有所不同。如果多运行几次,然后计算某个总数出现次数的百分比,会得到右图这样的结果:

 结果  一个11面的骰子  两个6面的骰子  2  9.1%  2.8%  3  9.1%  5.6%  4   9.1%  8.3%  5  9.1%  11.1%  6  9.1%  13.9%  7  9.1%  16.7%  8  9.1%  13.9%  9  9.1%  11.1%  10  9.1%  8.3%  11  9.1%  5.6%  12  9.1%  2.8% 

如果画出这些数字的一个图表,可以得到

为什么会有这种差别?原因与概率有关,这是一个庞大的主题。基本说来,对于两个骰子,中间的数更常出现,这是因为掷两个骰子时有更多途径可以得到中间这几个数。

掷两个骰子时,可能发生多种不同组合。以下是这些组合的列表,给出了它们的总数:

共有 36 种可能的组合。现在来看每个总数出现的次数:

 
  • 总数 2 出现 1 次;

  • 总数 3 出现 2 次;

  • 总数 4 出现 3 次;

  • 总数 5 出现 4 次;

  • 总数 6 出现 5 次;

  • 总数 7 出现 6 次;

  • 总数 8 出现 5 次;

  • 总数 9 出现 4 次;

  • 总数 10 出现 3 次;

  • 总数 11 出现 2 次;

  • 总数 12 出现 1 次。

这说明,掷出 7 比掷出 2 的途径更多。掷出 1+6、2+5、3+4、4+3、5+2 或 6+1 都可以得到 7。而要得到 2 只有一种情况,也就是掷出 1+1。所以这是有道理的,如果把这两个骰子掷出很多次,得到的 7 会比 2 更多。这也正是我们从两个骰子程序得到的结果。

使用计算机程序生成随机事件是研究概率的一种很好的方法,可以用来完成概率试验,查看需要相当多次尝试才能看到的结果。如果让你把一对真正的骰子掷 1000 次并记录结果,这会花费很长时间,但是计算机程序在远不到 1 秒的时间内就可以办到!

连续 10 次

继续学习接下来的内容之前,下面再做一个概率试验。前面我们讨论过扔硬币,并提到连续多次正面朝上的可能性有多大。为什么不试一试呢?看看连续 10 次正面朝上的情况多久出现一回?这种情况不常发生,所以我们必须扔很多很多次才能看到这种情况出现。为什么不试试扔 1 000 000 次!如果是一个真正的硬币,这可能要花……总之相当长的时间。

如果每 5 秒扔一次硬币,每分钟就会扔 12 次,或者每小时扔 720 次。如果一天扔 12 个小时的硬币(毕竟,你还得睡觉和吃饭),每天可以扔大约 8 500 次。所以扔一百万次硬币大约需要 115 天(约 4 个月)。不过利用计算机,我们几秒钟就可以完成。(嗯,也许要几分钟,因为我们还得先写出程序。)

在这个程序中,除了扔硬币,还必须跟踪什么时候可以得到连续 10 次正面朝上。有一种办法是使用一个用来完成统计的变量,即计数器(counter)。

我们需要两个计数器。一个用于统计连续扔出多少个正面朝上,名为 heads_in_row。另一个用于统计连续 10 次正面朝上的情况出现多少次,名为 ten_heads_in_row。程序要做的工作如下:

 
  • 得到正面朝上时,heads_in_row 计数器增 1;

  • 正面朝下时,heads_in_row 计数器还原为 0;

  • heads_in_row 计数器达到 10 时,将 ten_heads_in_row 计数器增 1,并设 置 heads_in_row 计数器还原为 0,重新开始;

  • 最后,打印一条消息,指出得到连续 10 次正面朝上的情况出现多少次。

代码清单 23-3 给出了完成这个工作的代码。

代码清单 23-3 查找连续 10 次正面朝上

运行这个程序时,得到这样的结果:

We got 10 heads in a row 510 times.

我运行了好几次这个程序,这个数总是在 500 左右。这说明,如果扔 100 万次硬币,连续 10 次正面朝上的情况大约出现 500 次,或者大约每扔 2000 次硬币就会得到连续 10 次正面朝上(1 000 000 / 500 = 2 000)。