那麼我們能夠用嵌套循環做些什麼呢?嗯,嵌套循環最擅長的工作就是得出一系列決定的所有可能的排列和組合。
術語箱
排列(permutation)是一個數學概念,表示結合一組事物的唯一方式。組合(combination)與它很類似。它們的區別在於,對於組合,順序並不重要,而排列中順序會有影響。
如果要從 1 到 20 選擇 3 個數,可以選擇
5, 8, 14
2, 12, 20
等等。如果想建立一個列表,列出從 1 到 20 選擇 3 個數的所有排列,下面這兩項是不同的:
5, 8, 14
8, 5, 14
這是因為,對於排列,元素出現的順序很重要。如果建立一個包含所有組合的列表,下面這些都會視為一項:
5, 8, 14
8, 5, 14
8, 14, 5
這是因為對於組合來說,順序並不重要。
要解釋這個問題,最好的辦法就是舉一個例子。下面假設你要在學校開春季交易會期間開個熱狗店,你想做個廣告海報,用數字顯示如何訂購熱狗、小麵包、番茄醬、芥末醬和洋蔥的所有可能的組合。所以我們需要得出總共有多少種可能的組合。
考慮這個問題的一種方法就是使用決策樹(decision tree)。下面的圖顯示了這個熱狗問題的決策樹。
每個決策點都有兩種選擇,是(Y)或者否(N)。這棵樹的每一條不同的路徑分別描述了熱狗各部分的不同的組合。這裡突出顯示的路徑是這樣選擇的:熱狗選擇 Y,小麵包選擇 N,芥末醬選擇 Y,番茄醬選擇 Y 。
現在我們使用嵌套循環來列出所有組合,也就是這棵決策樹的所有路徑。由於這裡有 5 個決策點,所以在我們的決策樹中有 5 層,相應地,在程序中就會有 5 個嵌套循環。(上圖只顯示了決策樹的前 4 層。)
在 IDLE 編輯器窗口中鍵入代碼清單 11-6 中的代碼,保存為 hotdog1.py。
代碼清單 11-6 熱狗組合
看到這些循環是如何一個套一個的了嗎?這正是嵌套循環,即一個循環放在另一個循環中。
外循環(熱狗循環)運行兩次。
對熱狗循環的每一次迭代,小麵包循環運行兩次,所以它會運行 2 × 2 = 4 次。
對小麵包循環的每一次迭代,番茄醬循環運行兩次,所以它會運行 2 × 2 × 2 = 8 次。
依此類推。
最內層循環(嵌套最深的循環,也就是洋蔥循環)會運行 2 × 2 × 2 × 2 × 2 = 32 次。這就涵蓋了所有可能的組合。因此共有 32 種可能的組合。
如果運行代碼清單 11-6 中的程序,會得到下面的結果:
>>> =========================== RESTART ===========================>>>Dog Bun Ketchup Mustard Onions# 1 0 0 0 0 0# 2 0 0 0 0 1# 3 0 0 0 1 0# 4 0 0 0 1 1# 5 0 0 1 0 0# 6 0 0 1 0 1# 7 0 0 1 1 0# 8 0 0 1 1 1# 9 0 1 0 0 0# 10 0 1 0 0 1# 11 0 1 0 1 0# 12 0 1 0 1 1# 13 0 1 1 0 0# 14 0 1 1 0 1# 15 0 1 1 1 0# 16 0 1 1 1 1# 17 1 0 0 0 0# 18 1 0 0 0 1# 19 1 0 0 1 0# 20 1 0 0 1 1# 21 1 0 1 0 0# 22 1 0 1 0 1# 23 1 0 1 1 0# 24 1 0 1 1 1# 25 1 1 0 0 0# 26 1 1 0 0 1# 27 1 1 0 1 0# 28 1 1 0 1 1# 29 1 1 1 0 0# 30 1 1 1 0 1# 31 1 1 1 1 0# 32 1 1 1 1 1
這 5 個嵌套循環可以得到熱狗、小麵包、番茄醬、芥末醬和洋蔥的所有可能的組合。
代碼清單 11-6 中,我們使用了製表符來實現對齊,也就是符號 t
。我們還沒有討論到打印格式,不過如果你想瞭解更多,可以先看看第 21 章。
這裡使用了一個名為 count
的變量對各個組合編號。例如,一個帶小麵包和芥末醬的熱狗就是 #27。當然,這 32 個組合中有些組合併沒有實際意義。(如果沒有小麵包,但有番茄醬和芥末醬,這樣的熱狗肯定會弄得一團糟。)要知道有句話是這麼說的:「顧客就是上帝!」
計算卡路里
因為如今所有人都很關心營養問題。下面為菜單上的每個組合增加一個卡路里計算。(你可能不太關心卡路里,不過我打賭你的爸爸媽媽一定很關心!)我們可以利用這個機會使用 Python 的一些數學功能(這在第 3 章學過)。
我們已經知道了每個組合裡有哪些項。現在需要的就是每一項的卡路里數。然後可以在最內層循環中把各項的卡路里數加起來。
下面的代碼設置了每一項有多少卡路里:
dog_cal = 140bun_cal = 120mus_cal = 20ket_cal = 80onion_cal = 40
現在只需要把它們加起來。我們知道每個菜單組合中各項要麼是 0 要麼是 1。所以可以直接將數量乘以每一項的卡路里,像這樣:
tot_cal = (dog * dog_cal) + (bun * bun_cal) + (mustard * mus_cal) + (ketchup * ket_cal) + (onion * onion_cal)
由於運算的先後順序是先算乘法再算加法,所以這裡原本不需要加括號。之所以加括號是為了更容易地看出這是怎麼做的。
長代碼行
注意到以上代碼中行末的反斜線()字符了嗎?如果有一個很長的語句,在一行裡放不下,就可以使用反斜線字符告訴 Python,「這一行還沒有結束。下一行的內容也是這一行的一部分」。這裡使用了兩個反斜線把一個長代碼行分成了 3 個小代碼行。反斜線也稱為行聯接符(line continuation character),很多編程語言都有這種行聯接符。
還可以在整個表達式前後兩邊額外加一對小括號,這樣不必使用反斜線也可以把語句分為多行,就像下面這樣:
tot_cal = ((dog * dog_cal) + (bun * bun_cal) + (mustard * mus_cal) + (ketchup * ket_cal) + (onion * onion_cal))
綜合上面的內容,增加卡路里計算的熱狗程序版本如代碼清單 11-7 所示。
代碼清單 11-7 能計算卡路里的熱狗程序
在 IDLE 中運行代碼清單 11-7 中的程序,應該能得到這樣的輸出:
>>> =========================== RESTART ===========================>>> Dog Bun Ketchup Mustard Onions Calories# 1 000000# 2 0000140# 3 0001020# 4 0001160# 5 0010080# 6 00101120# 7 00110100# 8 00111140# 9 01000120# 10 01001160# 11 01010140# 12 01011180# 13 01100200# 14 01101240# 15 01110220# 16 01111260# 17 10000140# 18 10001180# 19 10010160# 20 10011200# 21 10100220# 22 10101260# 23 10110240# 24 10111280# 25 11000260# 26 11001300# 27 11010280# 28 11011320# 29 11100340# 30 11101380# 31 11110360# 32 11111400
想想看,如果你自己手工計算所有這些組合的卡路里該是多麼枯燥,即使用計算器來完成數學運算也會很乏味。編寫一個程序,讓它幫你把這些都算出來就有意思多了。運用循環和 Python 中的一點數學運算,這個任務對它來說簡直是小菜一碟。
你學到了什麼
在這一章,你學到了以下內容。
嵌套循環。
可變循環。
排列和組合。
決策樹。
測試題
1. Python 中如何建立可變循環?
2. Python 中如何建立嵌套循環?
3. 下面的代碼總共會打印出多少星號:
for i in range(5): for j in range(3):print \'*\', print
4. 第 3 題中的代碼會得到什麼輸出?
5. 如果一個決策樹有 4 層,每層有兩個選擇,共有多少種可能的選擇(決策樹有多少條路徑)?
動手試一試
1. 還記得第 8 章創建的倒計時定時器程序嗎?在這兒呢,提醒你一下:
import timefor i in range (10, 0, -1): print i time.sleep(1)print \"BLAST OFF!\"
使用一個可變循環修改程序。這個程序要詢問用戶向下計數應當從哪裡開始,比如:
Countdown timer: How many seconds? 44321BLAST OFF!
2. 根據第 1 題寫的程序,讓它除了打印各個數之外還要打印一行星號,如下:
Countdown timer: How many seconds? 44 * * * *3 * * *2 * *1 *BLAST OFF!
(提示:可能需要使用一個嵌套循環。)