讀古今文學網 > 父與子的編程之旅:與小卡特一起學Python > 23.2 擲骰子 >

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。如果結果為 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 面的骰子擲 1000 次

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%

如果畫出這些數字的一個圖表,可以得到

為什麼會有這種差別?原因與概率有關,這是一個龐大的主題。基本說來,對於兩個骰子,中間的數更常出現,這是因為擲兩個骰子時有更多途徑可以得到中間這幾個數。

擲兩個骰子時,可能發生多種不同組合。以下是這些組合的列表,給出了它們的總數:

1 + 1= 21 + 2 = 31 + 3 = 41 + 4 = 51 + 5 = 61 + 6 = 72 + 1 = 32 + 2 = 42 + 3 = 52 + 4 = 62 + 5 = 72 + 6 = 83+ 1 = 43 + 2 = 53 +3 = 63 +4 = 73 +5 = 83 +6 = 94+1 = 54 + 2 = 64 + 3 = 74 +4 = 84 +5 = 94 +6 = 105+1 = 65+2 = 75+3 = 85+4 = 95+5 = 10 5+ 6 = 116+1 = 76 +2 = 86 +3 = 96 +4 = 10 6+5 = 11 6+6 = 12

共有 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 個小時的硬幣(畢竟,你還得睡覺和吃飯),每天可以扔大約 8640 次。所以扔一百萬次硬幣大約需要 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 = 2000)。