2021年3月11日 星期四

Python 學習筆記 : 基本語法 (二) : 流程控制

最近重新整理之前的 Python 基本語法筆記, 由於篇幅太長編修時載入時間較久, 決定將流程控制與分割出來. 本系列前一篇文章參考 : 


本篇繼續整理與測試 Python 基本語法之流程控制 : 


八. 流程控制 :

結構化程式設計主要包括下列三種語法結構 :
  • 循序 (sequential)
  • 分支 (branch)
  • 反覆 (repeat)
其中分支又稱為選擇 (selection) 結構 , 反覆又稱為迭代 (iteration) 或迴圈 (loop), 程式由上而下循序執行, 但遇到反覆或分支指令時就會停留或改變執行方向, 故分支與反覆屬於流程控制指令.


1. 分支 (branch) : 

分支使用條件式控制程式的執行方向, 語法如下 :

if 條件式1 :
    分支區塊1
[elif 條件式2 :
    分支區塊2]
[elif 條件式3 :
    分支區塊3]
....
[else :
    分支敘述]

中括號表示 elif 與 else 是可有可無的, 只有 if 時為單一分支; 只有 if 與 else為雙分支; 含有 if, 一個以上 elif, 以及 else 為多分支. Python 沒有 switch case 這種多分支指令, 只能使用 if elif else 指令來寫. 條件式會傳回 True 或 False, 可以是邏輯或比較運算式, 注意, 這些條件式在邏輯上必須是互斥 (exclusive), 否則必須將優先項目放在前面.

程式會由上而下依序判斷 if 與 elif 中的條件式, 當條件式為真時即執行縮排之分支區塊, 如果 if 與 elif 的條件式全部都不滿足才會執行 else 的分支區塊. 如果分支區塊裡面只有一個敘述, 可以直接寫在 if/elif/else 後面, 不須跳行縮排, 例如下面判斷成績等級的範例 :

score=int(input('請輸入成績:'))
if score >= 90 : print('優等')
elif score >= 80 : print('甲等')
elif score >= 70 : print('乙等')
elif score >= 60 : print('丙等')
else : print('丁等')

下列範例為金融商管應用上, 將景氣信號綜合分數轉換成景氣對策燈號 :

score=int(input('請輸入景氣對策信號綜合分數:'))
if score >= 38 : print('紅燈')
elif score >= 32 : print('黃紅燈')
elif score >= 23 : print('綠燈')
elif score >= 17 : print('黃藍燈')
else : print('藍燈')

這兩個範例的條件式並沒有互斥, 但把分數高的放在前面優先判斷 (若使用小於則分數高的要放後面), 所以結果仍然正確.

分支條件若為數值且形成範圍也可以用範圍條件式, 例如 :

score=int(input('請輸入成績:'))
if  90 <= score <= 100 : print('優等')
if  80 <= score < 90 : print('甲等')
if  70 <= score < 80 : print('乙等')
if  60 <= score < 70 : print('優等')
if  0 <= score < 60 : print('丙等')

注意, <= 與 >= 運算子等號都在右邊. 

Python 沒有像 Java 或 C, PHP 的條件運算式 : 

條件式 ? 運算式1 : 運算式2        (當條件式為真時執行運算式1, 否則執行運算式2)

但可以用單行條件式取代 : 

變數=值1 if 條件式 else 值2   

當條件式為真時變數=值1; 否則變數=值2, 例如 :

result="pass" if score >= 60 else "fail" 

但這與 Java 的條件運算式仍有差異, 即限制在同一變數之取值. 


2. 迴圈 (loop) : 

迴圈用來處理需重複執行的迭代工作 (iteration), 可減少程式的複雜度, 在語法上又稱為反覆結構, 主要由迴圈條件 (condition) 與迴圈主體 (即要重複執行之程式碼段) 構成. 迴圈條件用來控制迴圈結束之時機, 若設定不當將使程式進入無窮迴圈狀態.

Python 的迴圈指令有兩種 :
  • for 迴圈 : 進入迴圈前迴圈次數已確定
  • while 迴圈 : 進入迴圈前迴圈次數不確定
for 迴圈通常與內建函數 range() 配合進行整數循序迭代; 而 while 迴圈則配合條件式來控制迭代是否繼續.


(1). for 迴圈 : 

for 迴圈用在進入迴圈前迴圈次數已確定之場合, 其語法如下 :

for 迭代變數 in 迭代器 : 
    迴圈主體
[else : 
    跳出執行運算式]

注意, else 區塊可有可無, 用途是在完成迴圈時執行特定工作 (但以 break 跳出迴圈時不會執行 else 內之區塊), 參考 :

https://www.w3schools.com/python/python_for_loops.asp

下列容器都可以做為迭代器 :
  • 字串 (string) : 迭代變數值為字串中的字元
  • 元組 (tuple) : 迭代變數值為元素
  • 串列 (list) : 迭代變數值為元素
  • 字典 (dict) : 迭代變數值為鍵 (key)
  • range() 函數 : 迭代變數值為 range 物件之內容
最常用的迭代器為使用內建函數 range(start, end , step=1) 來產生的遞增整數可迭代物件, 此函數會傳回起始值為 start, 終止值為 end-1 (即開區間, 不含 end), 間隔為 step (預設=1) 的 range 物件. step 為負數時為遞減迭代器, 這時 start 必須比 end 值大才行, 例如 range(5, 0, -1) 表示從 5 遞減至 1, 即 [5, 4, 3, 2]. 

內建函數 range() 可以有 1 ~ 3 個參數 : 


 range() 函數 說明
 range(end) 傳回起始值 0 至 end-1 之可迭代 range 物件
 range(start, end) 傳回起始值 start 至 end-1 之可迭代 range 物件
 range(start, end, step) 傳回起始值 0 至 end-1, 步階 step 之可迭代 range 物件


例如下列範例為利用迴圈計算正整數數列 1, 2, 3, ... 100 之和 :

sum=0
for i in range(1, 101) :      #range() 傳回內容為 1~100 整數之可迭代 range 物件
    sum += i
else :
    print("1~%d 正整數數列和=%d" % (100, sum))  #結果為 5050

此例用 range(1, 101) 傳回的 [1, 2, 3, ... 100] 整數串列作為迭代器來累加等差數列, 並在跳出迴圈時輸出級數和結果.

下面範例為求兩個整數 268 與 580 的公因數, 公因數必定是介於 1 與較小的數之間, 因此只要用迴圈檢查 1 與 268 之間可同時整除 268 與 580 者即為兩者之公因數 :

for i in range(1, 268):
    if 268 % i == 0 and 580 % i == 0:
        print(i)

此處用餘數運算子 % 檢查餘數是否為 0 (表示整除), 結果為 1, 2, 4 三數. 

注意, 迭代器為字典時迭代變數為字典的鍵 (不是值), 例如 :

fruits={'蘋果':'apple','西瓜':'watermelon','香蕉':'banana'}
for fruit in fruits :
    print('%s : %s' % (fruit, fruits[fruit]))

此例迭代變數 fruit 為字典變數 fruits 之鍵, 輸出結果為 :

蘋果 : apple
西瓜 : watermelon
香蕉 : banana

兩層以上迴圈稱為巢狀迴圈 (nesting loop), 需注意內外層迭代變數之不同, 例如用雙層迴圈列印九九乘法表 :

for i in range(1,10):
    for j in range(1,10):
        print('%d*%d=%2d' %(i, j, i*j), end=' ')  #end 為一空格
    print('\n')  #跳行

此程式外層迭代變數為 i, 內層迭代變數為 j, 內建函數 range(1,10) 會傳回 1~9 的整數串列作為迭代器, 利用 print() 函數控制列印格式, 結果如下 :

1*1= 1 1*2= 2 1*3= 3 1*4= 4 1*5= 5 1*6= 6 1*7= 7 1*8= 8 1*9= 9
2*1= 2 2*2= 4 2*3= 6 2*4= 8 2*5=10 2*6=12 2*7=14 2*8=16 2*9=18
3*1= 3 3*2= 6 3*3= 9 3*4=12 3*5=15 3*6=18 3*7=21 3*8=24 3*9=27
4*1= 4 4*2= 8 4*3=12 4*4=16 4*5=20 4*6=24 4*7=28 4*8=32 4*9=36
5*1= 5 5*2=10 5*3=15 5*4=20 5*5=25 5*6=30 5*7=35 5*8=40 5*9=45
6*1= 6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36 6*7=42 6*8=48 6*9=54
7*1= 7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49 7*8=56 7*9=63
8*1= 8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64 8*9=72
9*1= 9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81


(2). while 迴圈 : 

while 迴圈用在進入迴圈前迴圈次數已確定之場合, 其語法如下 :

while (條件式) :
    迴圈主體
[else:
    跳出執行運算式]

在條件式為真時會反覆執行迴圈主體中的程式碼區塊, 否則跳出迴圈, 跳出時執行 else 區塊  (但以 break 跳出迴圈不會執行 else 內之區塊). 注意, 迴圈主體中應進行條件變更, 避免成為無窮迴圈, 例如 :

i=0
while (i<6) :
    print(i)
    i=i+1

此程式變數 i 有一個初始值 0, 進入迴圈時先用條件式 i< 6 判斷真假, 為真才執行迴圈主體內之敘述, 迴圈內對 i 增量進行條件變更, 重複執行迴圈值到條件式為假時跳出迴圈.


(3). 迴圈中斷指令 break 與 continue :

不論是 for 或 while 迴圈, 在迴圈內部還可以在滿足特定條件式時利用 break 或 continue 指令來中斷迴圈, 不過 break 與 continue 中斷的作用不同, 執行 break 指令會跳出迴圈; 而執行 continue 指令則停止目前迴圈, 忽略後面到迴圈結尾的指令, 回到迴圈開始的地方直接進入下一個迴圈.

例如計算 1~100 間的奇數和 s=1+3+5+7+....+99 可在迴圈中用 continue 跳過偶數 :

s=0
for i in range(1, 101):  # 迭代 1~100 的迴圈 
    if i % 2 == 0:           # 跳過偶數 (可被 2 整除)
        continue  
    s += i
print(s)       # 輸出 2500

此例在 i 可被 2 整除時執行 continue 直接進入下一個迭代迴圈, 這樣就把偶數跳過去不累加. 若將條件式改為 if i % 2 != 0 則是跳過奇數, 變成累加偶數 s=2+4+6+8+....+100, 結果為 2550. 

不過在 "Effective Python" 這本書裡, 作者建議在 for 或 while 迴圈中最好不要使用 else 區塊, 因為其語意容易與 if else 或 try except else 中的 else 混淆而使程式解讀或維護造成困擾.


(4). 無窮迴圈 : 

無窮迴圈會持續不斷執行迴圈內的敘述, 直到使用者按下 CTRL+C 強制中斷程序為止, 除非必要, 否則使用迴圈時應避免使其成為無窮迴圈.

Python 的無窮迴圈可用 for 或 while 迴圈 :

while (true) :
    敘述


(1). while 無窮迴圈 :   

迴圈條件式之值永遠為真的迴圈稱為無窮迴圈, 此種程序不會停止, 只能透過 CTRL+C 強制終止程序. 下面為參考 "Creative Coding in Python" 一書中的猜數字範例修改而來 :

secret_number=87
n=int(input('輸入 1~100 的數字:'))
while not (n==secret_number):
    if n > secret_number:
        print('您猜的數字太大了!')
    else:
        print('您猜的數字太小了!')
    n=int(input('再輸入 1~100 的數字:'))
print('您猜對了')

下面範例綜合使用分支與迴圈來檢查身分證字號是否正確, 台灣身分證共 10 個字元, 格式如下 (例如 A123456789) :


 英文字母 N1 N2 N3 N4 N5 N6 N7 N8 N9


第一個字元為英文字母, 代表出生地, 其他 9 個字元均為數字, 第一個數字 N1 代表性別, 1 表示男性, 2 表示女性, 其餘 8 個 N2~N9 是身分證號碼, 檢查身分證正確性的演算法是先將英文字母依據下列對應表轉成10~35 的數值 (出生地為舊行政區劃) :


 字母 數值 (X1X2) 出生地
 A 10 台北市
 B 11 台中市
 C 12 基隆市
 D 13 台南市
 E 14 高雄市
 F 15 台北縣
 G 16 宜蘭縣
 H 17 桃園縣
 I 34 嘉義市
 J 18 新竹縣
 K 19 苗栗縣
 L 20 台中市
 M 21 南投縣
 N 22 彰化縣
 O 35 新竹市
 P 23 雲林縣
 Q 24 嘉義縣
 R 25 台南縣
 S 26 高雄縣
 T 27 屏東縣
 U 28 花蓮縣
 V 29 台東縣
 W 32 金門縣
 X 30 澎湖縣
 Y 31 陽明山
 Z 33 連江縣


驗證身分證字號的演算法是將英文字母對應數值 (X1X2) 的個位數 X2 乘以 9 加上十位數 X1, 然後將其餘數字由左到右分別乘以 8, 7, 6, 5, ...., 1, 1, 然後全部加總起來 :

S=X1 + X2*9 + N1*8 + N2*7 + N3*6 + N4*5 + N5*4 + N6*3 + N7*2 + N8*1 + N9

此總和若能被 10 整除即為合法之身分證字號, 程式如下 :

id=input('請輸入身分證字號:')
id=id.upper()   #轉成大寫
x1x2={'A':'10','B':'11','C':'12','D':'13','E':'14','F':'15','G':'16',
'H':'17','I':'34','J':'18','K':'19','L':'20','M':'21','N':'22','O':'35',
'P':'23','Q':'24','R':'25','S':'26','T':'27','U':'28','V':'29','W':'32',
'X':'30','Y':'31','Z':'33'}   #英文字母數值對應表
N=len(id)  #身分證字號長度
correct_format=(N==10) and (id[0].isalpha()) and (id[1:N].isdigit())
if correct_format:  #格式正確才進行驗證
    acode=x1x2.get(id[0])   #取得英文字母代碼
    x1=int(acode[0])        #取得字母代碼十位數
    x2=int(acode[1])        #取得字母代碼個位數
    i=1     #id[] 數字索引起始值 id[0] 為字母
    sum=0   #總和初始值=0
    for n in range(8, 0, -1):         #迭代 n=8,7,6,5,4,3,2,1
        sum=sum + int(id[i])* n       #計算 N1 到 N8 加權之和
        i += 1                        #索引增量
    sum=x1 + x2*9 + sum + int(id[i])  #驗證碼總和 (末項為 N9)
    print("總和=", sum)
    remainder=sum % 10
    if remainder==0:
        print("身分證字號正確")
    else:
        print("身分證字號不正確")
else:
    print("身分證字號格式 : 1個英文字母+9個阿拉伯數字")

執行結果如下 :

請輸入身分證字號:a123456789
總和= 130
身分證字號正確
請輸入身分證字號:s121905222
總和= 152
身分證字號不正確

參考 :

python 程式設計50題測試範例-15
檢查台灣身份證號碼

沒有留言 :