2020年10月29日星期四

011 與小卡特一起學 Python 第 11 章 嵌套與可變迴圈

 申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!


11 章 嵌套與可變迴圈

我們已經看到了,在循環體(也就是代碼塊)中可以放入其他代碼,這些代碼本身也可以有自己的代碼塊。如果查看第 1 章中的猜數程式,可以看到:


外層淺灰色的塊是一個 while 迴圈塊,深灰色的塊是這個 while 迴圈塊中的 if  elif 塊。

還可以把一個迴圈放在另一個迴圈中。這些迴圈就叫做嵌套迴圈(nested loop)。

 

11.1 嵌套迴圈

還記得第 8 動手試一試中你寫的乘法表程式嗎?如果不考慮使用者輸入部分,代碼會是這樣:

multiplier = 5

for i in range (1, 11):

    print i, "x", multiplier, "=", i * multiplier

 

如果想一次列印 3 個乘法表呢?這種事情正是嵌套迴圈最擅長的。嵌套迴圈就是一個迴圈出現在另一個迴圈裡。對於外迴圈的每次反覆運算,內迴圈都要完成它的所有反覆運算。

要列印 3 個乘法表,只需要把原來的迴圈(列印一個乘法表)包含在一個外迴圈中(運行 3 次)。這樣,程式就會列印 3 個乘法表而不只是一個。代碼清單 11-1 顯示了相應的代碼。

代碼清單 11-1 一次列印 3 個乘法表


注意必須將內迴圈縮進,而且 print 語句距外部 for 迴圈開始位置還要多加 4 個空格。這個程式會分別列印 56 7 的乘法表,每個表分別從 1 乘到 10

>>> ==================== RESTART ===================

>>> 

1 x 5 = 5

2 x 5 = 10

3 x 5 = 15

4 x 5 = 20

5 x 5 = 25

6 x 5 = 30

7 x 5 = 35

8 x 5 = 40

9 x 5 = 45

10 x 5 = 50

 

1 x 6 = 6

2 x 6 = 12

3 x 6 = 18

4 x 6 = 24

5 x 6 = 30

6 x 6 = 36

7 x 6 = 42

8 x 6 = 48

9 x 6 = 54

10 x 6 = 60

 

1 x 7 = 7

2 x 7 = 14

3 x 7 = 21

4 x 7 = 28

5 x 7 = 35

6 x 7 = 42

7 x 7 = 49

8 x 7 = 56

9 x 7 = 63

10 x 7 = 70

 

可以在螢幕上列印一些星號,並統計有多少個,你可能認為這很沒意思,不過要瞭解嵌套迴圈到底是怎麼回事,這確實是一個很好的辦法。在下一節,我們就來完成這個工作。

 

11.2 可變迴圈

固定的數(比如 range() 函數中使用的數)也稱為常數(constant)。如果在一個 for 迴圈的 range() 函數中使用常數,程式運行時迴圈總會運行相同的次數。在這種情況下,我們稱迴圈次數是硬編碼的(hard-coded),因為它在你的代碼中被定義了,而且永遠不會改變。這往往不是我們真正想要的。


有時我們希望迴圈次數由用戶來決定,或者由程式的另一部分決定。對於這種情況,我們就需要一個變數。

例如,假設你要建立一個太空神槍手遊戲。只要有外星人被消滅就要重繪螢幕。必須有某個計數器來跟蹤還剩下多少外星人,另外只要螢幕更新,就需要迴圈處理剩下的外星人,在螢幕上畫出他們的圖像。每次玩家消滅一個外星人時外星人數就會改變。

因為我們還沒有學習如何在螢幕上畫外星人,下面先給出一個使用可變迴圈的簡單示例程式:

for i in range (1, numStars):

     print '*',

 

 

>>> ====================== RESTART ======================

>>> 

How many stars do you want? 5

* * * *

 

這個程式會詢問使用者想要多少個星號,然後使用一個可變迴圈準確地列印這些星號。嗯,只能算基本準確!我們想要 5 個星號,可是只得到了 4 個!唉呀,我們忘記了 for 迴圈不是達到 range 函數中第二個數時才停止,它在比這個數少 1 時就停止了。所以需要對使用者的輸入加 1


還有一種方法可以完成同樣的工作,就是從 0 開始迴圈計數,而不是 1。(這一點在第 8 章提到過。)這種做法在程式設計中很常用,下一章會解釋為什麼。先來看看這個迴圈是怎樣的:

numStars = int(raw_input ("How many stars do you want? "))

for i in range(0, numStars):

     print '*',

 

>>> ====================== RESTART =====================

>>> 

How many stars do you want? 5

* * * * *

 

11.3 可變嵌套迴圈

現在來嘗試一個可變嵌套迴圈。這就是一個嵌套迴圈,只不過其中一個或多個迴圈在 range() 函數中使用了變數。代碼清單 11-2 給出了一個例子。

代碼清單 11-2 一個可變嵌套迴圈

numLines = int(raw_input ('How many lines of stars do you want? '))

numStars = int(raw_input ('How many stars per line? '))

for line in range(0, numLines):

    for star in range(0, numStars):

        print '*',

    print

 

運行這個程式來看它的作用,你會看到類似這樣的結果:

>>> ============================ RESTART ============================

>>> 

How many lines of stars do you want?  3

How many stars per line?  5

*****

*****

*****

 

前兩行詢問用戶想要多少行,以及每行希望有多少個星號。程式使用變數 numLines  numStars 記住這些答案。接下來有兩個迴圈:

·    內迴圈(for star in range (0, numStars):)列印每個星號,對每一行上的每個星號分別運行一次;

·    外迴圈(for line in range (0, numLines):)對每行星號分別運行一次。

需要用第二個 print 命令開始新的一行星號。如果沒有這個命令,由於第一個 print 語句中有逗號,所有星號都會列印到同一行上。

甚至可以有嵌套嵌套迴圈(或雙重嵌套迴圈),就像代碼清單 11-3 這樣。

代碼清單 11-3 利用雙重嵌套迴圈生成星號塊

numBlocks = int(raw_input ('How many blocks of stars do you want? '))

numLines = int(raw_input ('How many lines in each block? '))

numStars = int(raw_input ('How many stars per line? '))

for block in range(0, numBlocks):

    for line in range(0, numLines):

        for star in range(0, numStars):

            print '*',

        print

print

 

會得到下面的輸出:

>>> ======================= RESTART =======================

>>> 

How many blocks of stars do you want? 3

How many lines of stars in each block? 4

How many stars per line? 8

* * * * * * * *

* * * * * * * *

* * * * * * * *

* * * * * * * *

 

* * * * * * * *

* * * * * * * *

* * * * * * * *

* * * * * * * *

 

* * * * * * * *

* * * * * * * *

* * * * * * * *

* * * * * * * *

 

我們稱這個迴圈嵌套深度為 3”

 

11.4 更多可變嵌套迴圈

代碼清單 11-4 是代碼清單 11-3 的一個更複雜的版本。

代碼清單 11-4 更複雜的星號塊


輸出如下:

>>> ======================= RESTART =======================

>>> 

How many blocks of stars do you want? 3

* * * *

 

* * * * *

* * * * * * *

* * * * * * * * *

 

* * * * * * *

* * * * * * * * *

* * * * * * * * * * *

* * * * * * * * * * * * *

* * * * * * * * * * * * * * *

 

代碼清單 11-4 中,外迴圈的迴圈變數用來為內迴圈設置範圍。所以每個星號塊不再有相同的行數,而且每一行也不再有相同的星號數,每次迴圈時行數和星號數都不同。

你希望迴圈嵌套多深,就可以有多深。要明白這樣的嵌套迴圈會讓人很頭疼,所以有時列印出迴圈變數的值會很有説明,如代碼清單 11-5 所示。

代碼清單 11-5 在嵌套迴圈中列印迴圈變數


以下是這個程式的輸出:

>>> ======================= RESTART =======================

>>> 

How many blocks of stars do you want? 3

block =  1

* * *   line  =  1 star = 3

 

block =  2

* * * * *   line =  1  star = 5

* * * * * * *   line =  2  star = 7

* * * * * * * * *   line =  3  star = 9

 

block =  3

* * * * * * *   line =  1  star = 7

* * * * * * * * *   line =  2  star = 9

* * * * * * * * * * *   line =  3  star = 11

* * * * * * * * * * * * *   line =  4  star = 13

* * * * * * * * * * * * * * *   line =  5  star = 15

 

在很多情況下,而不只限於在迴圈中,列印變數的值都會對你很有説明。這也是最常用的調試方法之一。

 



11.5 使用嵌套迴圈

那麼我們能夠用嵌套迴圈做些什麼呢?嗯,嵌套迴圈最擅長的工作就是得出一系列決定的所有可能的排列和組合。

術語箱

排列(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 = 140

bun_cal = 120

mus_cal = 20

ket_cal = 80

onion_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       0        0        0        0        0        0

# 2       0        0        0        0        1        40

# 3       0        0        0        1        0        20

# 4       0        0        0        1        1        60

# 5       0        0        1        0        0        80

# 6       0        0        1        0        1        120

# 7       0        0        1        1        0        100

# 8       0        0        1        1        1        140

# 9       0        1        0        0        0        120

# 10      0        1        0        0        1        160

# 11      0        1        0        1        0        140

# 12      0        1        0        1        1        180

# 13      0        1        1        0        0        200

# 14      0        1        1        0        1        240

# 15      0        1        1        1        0        220

# 16      0        1        1        1        1        260

# 17      1        0        0        0        0        140

# 18      1        0        0        0        1        180

# 19      1        0        0        1        0        160

# 20      1        0        0        1        1        200

# 21      1        0        1        0        0        220

# 22      1        0        1        0        1        260

# 23      1        0        1        1        0        240

# 24      1        0        1        1        1        280

# 25      1        1        0        0        0        260

# 26      1        1        0        0        1        300

# 27      1        1        0        1        0        280

# 28      1        1        0        1        1        320

# 29      1        1        1        0        0        340

# 30      1        1        1        0        1        380

# 31      1        1        1        1        0        360

# 32      1        1        1        1        1        400

 

想想看,如果你自己手工計算所有這些組合的卡路里該是多麼枯燥,即使用計算器來完成數學運算也會很乏味。編寫一個程式,讓它幫你把這些都算出來就有意思多了。運用迴圈和 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 time

for i in range (10, 0, -1):

    print i

    time.sleep(1)

print "BLAST OFF!"

 

使用一個可變迴圈修改程式。這個程式要詢問使用者向下計數應當從哪裡開始,比如:

Countdown timer:  How many seconds?  4

4

3

2

1

BLAST OFF!

 

2. 根據第 1 題寫的程式,讓它除了列印各個數之外還要列印一行星號,如下:

Countdown timer:  How many seconds? 4

4 * * * *

3 * * *

2 * *

1 *

BLAST OFF!

 

(提示:可能需要使用一個嵌套迴圈。)

 

 


0 留言:

發佈留言