迴圈與迭代

When you’ve given the same in-person advice 3 times, write a blog post.

David Robinson

資料結構中我們已經知道使用不同的資料結構,包含 list、tuple、set 與 dict 來儲存多個純量資料,像是整數(int)、浮點數(float)、文字(str)與布林(bool);有關應用這些儲存在結構中的大量資訊,常會有反覆執行、需要大量手動複製貼上程式的需求,這時可以應用迴圈(loop)或稱做迭代(iteration)的技法來協助。

舉例來說,在 fav_nba_players 這個 list 中儲存了多位超級 NBA 球星(退役或現役。)

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
print(fav_nba_players)
1
2
## ['Steve Nash', 'Michael Jordan', 'LeBron James', 'Dirk Nowitzski', 'Hakeem Olajuwon']
1

我們希望可以印刷這些球星的球衣,在球衣的設計上,除了會印製背號以外,亦會印製球員的姓氏(family name);像是 LeBron James 的球衣,除了 23 號還會有「JAMES」字樣。

NBA Store

因此我們需要想辦法將每個球員的姓氏從 fav_nba_players 中取出,再把姓氏所有字母轉換為大寫(upper-cased)。在不撰寫迴圈的情況下,我們還是可以利用 [INDEX] 土法煉鋼依照索引值將球員名字選出,利用 .split() 方法將姓氏與名字以空格切割,再以 [1] 選出姓氏,最後以 .upper() 方法轉換為大寫。

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
print(fav_nba_players[0].split()[1].upper())
print(fav_nba_players[1].split()[1].upper())
print(fav_nba_players[2].split()[1].upper())
print(fav_nba_players[3].split()[1].upper())
print(fav_nba_players[4].split()[1].upper())
1
2
3
4
5
6
## NASH
## JORDAN
## JAMES
## NOWITZSKI
## OLAJUWON
1
2
3
4
5

好險現在 fav_nba_players 只有五位球員,還可以勉強手動複製貼上,萬一現在我們要印製的是全 NBA 聯盟球員的球衣,那可有 300 至 400 個球員不等,該怎麼才好?當我們發現自己在進行複製貼上程式碼的時候,應該稍微把視線挪移開螢幕,想一下能怎麼樣把這段程式用迴圈與迭代來實踐。

運用 for 解決重複的任務

碰到需要大量手工複製貼上的事情,可以求助迴圈與迭代來幫助我們。首先登場的 for 迴圈外觀架構長得像這樣。

for ITERATOR in ITERABLE:
  # 重複執行的程式,直到 ITERATOR 到達 ITERABLE 的尾端
1
2

為與 Python 由零起始計算索引對照,我們重複迭代執行的次數也由零開始;在第零次的迭代中,ITERATOR 是 ITERABLE[0]、在第一次的迭代中,ITERATOR 是 ITERABLE[1];以此類推至第 N 次的迭代,ITERATOR 是 ITERABLE[N1],其中 ITERATOR 是一個純量,而 ITERABLE 可能是長度為 N 的 list、tuple、set、dict 或者 str,這裡並沒有寫錯,在純量中唯一可以被迭代的就是文字。

在目前的例子中,ITERATOR 必須由 0、1 經過 5 次的迭代更動至 4,因此可以設計一個 ITERATOR 名稱為 i,ITERABLE 要含有 0 到 4 這五個整數,可以利用 range(5) 產生,在每一次迭代時都會執行縮排裡面的程式 print(fav_nba_players[i].split()[1].upper())

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
for i in range(5):
  print(fav_nba_players[i].split()[1].upper())
1
2
3
## NASH
## JORDAN
## JAMES
## NOWITZSKI
## OLAJUWON
1
2
3
4
5

也能夠設計一個 ITERATOR 名稱為 player ,ITERATBLE 是一個含有 5 個喜愛 NBA 球星的文字 list fav_nba_players ,在每一次迭代時都會執行縮排裡面的程式 print(player.split()[1].upper())

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
for player in fav_nba_players:
  print(player.split()[1].upper())
1
2
3
## NASH
## JORDAN
## JAMES
## NOWITZSKI
## OLAJUWON
1
2
3
4
5

運用 while 解決重複的任務

接著登場的 while 迴圈外觀架構長得像這樣。

while BOOL:
  # 重複執行的程式,直到 BOOL 為 False
1
2

在每一次的迭代之前,Python 都會去檢查 while 保留字後面的布林是否為 True False 就會離開迴圈。

i = 0
fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
while i < 5:
  print(fav_nba_players[i].split()[1].upper())
  i += 1
1
2
3
4
5
## NASH
## JORDAN
## JAMES
## NOWITZSKI
## OLAJUWON
1
2
3
4
5

在 while 迴圈中很重要的一行程式為 i += 1 ,假如忘記寫這一行程式,我們的 while 迴圈會無限次數地一直輸出 "NASH",原因是布林永遠判斷為 True (0 < 5),所以不斷地執行 print(fav_nba_players[0].split()[1].upper())

兩種迴圈的運用時機

那麼在實際撰寫 Python 程式的時候,我們何時應該運用 for、何時應該運用 while 呢?一個簡單的判斷是:假如我們明確知道程式需要執行幾次(迭代次數),比如說我們知道 fav_nba_players 這個 list 的長度為 5,就可以採用 for 或 while,端看個人偏好;而在不知道迭代次數的情形下,我們就只能被迫採用 while。

這個說法還是略嫌抽象,舉一個例子說明會比較好理:假如現在投擲一個公正的骰子(裡頭沒有灌鉛或水銀),想知道總共需要投擲幾次才能夠將 1 到 6 點都至少投出一次,這就是一個不知道迭代次數的問題。運氣超好也許投擲六次每個點數就各自恰巧出現一次;運氣差一點的也許要投擲一、二十次才湊得齊所有點數,我們寫一段程式來模擬這個過程。程式中得使用 Python 內建模組 random 中 choice() 函數來擲骰子(從由 1 到 6 六個數字組合合成的 list 中隨機選出一個),再用 Python 的 set 資料結構來偵測過往的投擲結果是否有六個不同的數值(點數。)

from random import choice

dice = range(1, 7) # 1 代表 1 點、2 代表 2 點,以此類推
history = []
while len(set(history)) < 6:
  roll = choice(dice) # 投擲!
  history.append(roll)
# 將結果印出
print("總共投擲了 {} 次".format(len(history)))
print(history)
1
2
3
4
5
6
7
8
9
10
## 總共投擲了 10 次
## [5, 3, 1, 1, 4, 6, 1, 3, 3, 2]
1
2

以這次執行結果來看,共投了 10 次才將 6 個點數至少投出一次;歷次投擲分別為 5 點、3 點、1 點、1 點、4 點、6 點、1 點、3 點、3 點與 2 點,您可以試著自行執行幾輪,看看每一輪投擲次數的差異。

如何迭代不同的 iterables

在 Python 中所謂的 iterables 可以是長度為 N 的 list、tuple、set、dict 或者 str,其中迭代 list、tuple、set 與 str 的方式都相同。

# 如何迭代 list
fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
print(type(fav_nba_players))
for player in fav_nba_players:
  print(player)
print("======")  
# 如何迭代 tuple
fav_nba_players = ("Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon")
print(type(fav_nba_players))
for player in fav_nba_players:
  print(player)
print("======")  
# 如何迭代 set
fav_nba_players = {"Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"}
print(type(fav_nba_players))
for player in fav_nba_players:
  print(player)
print("======")  
# 如何迭代 str
fav_player = "Steve Nash"
print(type(fav_player))
for char in fav_player:
  print(char)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## <class 'list'>
## Steve Nash
## Michael Jordan
## LeBron James
## Dirk Nowitzski
## Hakeem Olajuwon
## ======
## <class 'tuple'>
## Steve Nash
## Michael Jordan
## LeBron James
## Dirk Nowitzski
## Hakeem Olajuwon
## ======
## <class 'set'>
## Hakeem Olajuwon
## Steve Nash
## Michael Jordan
## LeBron James
## Dirk Nowitzski
## ======
## <class 'str'>
## S
## t
## e
## v
## e
##  
## N
## a
## s
## h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

但 dict 是具有標籤(keys)與資料(values)的資料結構,在迭代時需要特別留意,預設 dict 的 iterable 是它的標籤,而 .keys().values().items() 則分別可以取出標籤、資料與標籤加上資料,其中 .items() 會將標籤以及資料兩兩合併在一個 tuple 中。

# 如何迭代 dict: default
fav_nba_players = {
    "Hair Canada": "Steve Nash",
    "Air Jordan": "Michael Jordan",
    "The King": "LeBron James",
    "The Big German": "Dirk Nowitzski",
    "The Dream": "Hakeem Olajuwon"
}
print(type(fav_nba_players))
for k in fav_nba_players:
  print(k)
print("===")
# 如何迭代 dict: .keys()
for k in fav_nba_players.keys():
  print(k)
print("===")
# 如何迭代 dict: .values()
for v in fav_nba_players.values():
  print(v)
print("===")
# 如何迭代 dict: .items()
for k, v in fav_nba_players.items():
  print("{} is as known as {}.".format(v, k))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## <class 'dict'>
## Hair Canada
## Air Jordan
## The King
## The Big German
## The Dream
## ===
## Hair Canada
## Air Jordan
## The King
## The Big German
## The Dream
## ===
## Steve Nash
## Michael Jordan
## LeBron James
## Dirk Nowitzski
## Hakeem Olajuwon
## ===
## Steve Nash is as known as Hair Canada.
## Michael Jordan is as known as Air Jordan.
## LeBron James is as known as The King.
## Dirk Nowitzski is as known as The Big German.
## Hakeem Olajuwon is as known as The Dream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

結合程式分支與迴圈迭代

在撰寫時將流程控制的兩個技巧:程式分支與迴圈迭代合併使用會讓我們的需求更有彈性, 亦可以運用保留字 break 或者 continue 來協助。保留字 break 能夠讓我們在迴圈迭代的過程中在布林為 True 的時候(亦即滿足某個判斷條件)離開迴圈,舉例來說將 fav_nba_players 一一印出時,假如碰到 "LeBron James" 就離開迴圈(因為在撰寫文章的時候只有他是清單中唯一現役的超級球星)。

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
for player in fav_nba_players:
  if player == "LeBron James":
    break # 碰到 "LeBron James" 就離開迴圈
  else:
    print(player)
1
2
3
4
5
6
## Steve Nash
## Michael Jordan
1
2

保留字 continue 能夠讓我們在迴圈迭代的過程中在布林為 True 的時候(亦即滿足某個判斷條件)略過該次迭代但是繼續完成迴圈,舉例來說將 fav_nba_players 一一印出時,假如碰到 "LeBron James" 就略過該次迭代(因為在撰寫文章的時候只有他是清單中唯一現役的超級球星)。

fav_nba_players = ["Steve Nash", "Michael Jordan", "LeBron James", "Dirk Nowitzski", "Hakeem Olajuwon"]
for player in fav_nba_players:
  if player == "LeBron James":
    continue # 碰到 "LeBron James" 就略過
  else:
    print(player)
1
2
3
4
5
6
## Steve Nash
## Michael Jordan
## Dirk Nowitzski
## Hakeem Olajuwon
1
2
3
4

小結

在這個小節中我們簡介 Python 為因應重複執行程式需求所發展的迴圈與迭代,運用 for 或 while 迴圈解決重複的任務、兩種迴圈的運用時機、如何迭代不同的 iterables 以及結合程式分支與迴圈迭代。

延伸閱讀