iT邦幫忙

2

【python入門教室】(8) 如果你願意一層一層一層 的剝開繁瑣邏輯 (化簡冗長的if-else教學)

if- else 程式碼可說是工程師使用頻率很高,
卻也容易寫壞的一個例子,
過於多層的if-else嵌套,
容易使程式邏輯複輯,難以除錯
這一篇從語法及邏輯出發,教大家如何避免寫出冗長的if-else。
(基礎知識可參考: 【Python 超入門】(6) if-else邏輯: 人生最常做的事,不是吃飯睡覺,而是______ )

跟著小馬腳步由淺入深攻略if-else的技巧吧。

舉個簡單的例子,
例如希望寫一個函數判斷一個整數是否是偶數,
常會看到經驗不足的人順手寫出以下程式:

def isEven(n):
    if n%2 == 0:
        return True
    else:
        return False

看起來是沒有什麼問題,
但其實n%2 == 0本身是一個條件判斷式,
n%2 == 0 的值已經是True或是False了,
因此這段程式可以簡單用一句話來寫:

def isEven(n):
    return n%2 == 0

夠簡單吧?
以下舉幾個實例教大家化簡if-else,
希望能給大家一些啟發。

技巧1 – 使用三元運算式語法

例如現在我們想要判斷一個變數型態是否為整數,
我們可以用type()函數檢查一個變數的型態。
化簡前:

n = 5
T=""
if type(n)==int:
    T="n是整數"
else:
    T="n不是整數"

化簡後:

n = 5
T= "n是整數" if type(n)==int else "n不是整數"

這個給個說明,
三元運算式的語法為:

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

如果條件式的值為True,變數就會等於值1
否則變數等於值2
注意使用三元運算子語法時,
if-else一定要成對出現。

技巧2 – 利用in合併多個條件式

例如現在給你一個單字,
要你判斷這個單字的字首是不是母音('a','e','i','o','u')。
化簡前:

word = "apple"
if word[0] == 'a' or word[0] == 'e' or word[0] == 'i' or word[0] == 'o' or word[0] == 'u':
    print("單字開頭為母音")

五個判斷式同樣以word[0]這個字來做等式判斷,
實在太冗長了。
可以用in做改寫會簡單許多。

化簡後:

word = "apple"
if word[0] in 'aeiou':
    print("單字開頭為母音")

in這個關鍵字除了平時較常用的與for搭配的語法外(例如for i in range(3):),
亦可作為條件判斷,
A in B即是判斷物件A是否在容器B裡,
例如:

>>> 1 in [1,2,3]
True
>>> "a" in "apple"
True

技巧3 – 利用列表或字典化簡大量if-else if

範例: 十二生肖

民國1年是鼠年,
想要寫一個程式判斷今年是什麼年(鼠、牛、虎、兔、…)。
化簡前:

year = int(input())
if year%12 == 1:
    print("今年是鼠年")
elif year%12 == 2:
    print("今年是牛年")
elif year%12 == 3:
    print("今年是虎年")
# …
#省略部分程式碼
# …
elif year%12 == 11:
    print("今年是狗年")
elif year%12 == 0:
    print("今年是豬年")

哇,光是判斷十二生肖就寫了12個if-elif了,
真是非常不簡潔呢,
我們看看如何簡化它。

化簡後:

year = int(input())
S = ["豬","鼠","牛","虎","兔","龍","蛇","馬","羊","猴","雞","狗"]
print("今年是"+S[year%12]+"年")

改成用列表取index的方式來做,
把if-elif全部省略了,
是不是非常簡潔易讀呢?

範例: 撲克花色

假設我們想把英文字母與撲克牌的花色做轉換,
且看以下程式:
化簡前:

letter = input()
suit = ""
if letter == 's':
    suit = "黑桃"
elif letter == 'h':
    suit = "紅心"
elif letter == 'd':
    suit = "方塊"
elif letter == 'c':
    suit = "梅花"

這時便很適合用python的字典化簡它。
化簡後:

letter = input()
D = {'s':"黑桃", 'h':"紅心", 'd':"方塊", 'c':"梅花"}
suit = D[letter]

技巧4 – 化簡多重嵌套if else

不知道大家有看過遊戲王,
或是玩過卡片對戰類型的遊戲嗎?
這類遊戲因為卡片效果複雜的交互作用,
若是寫成if-else邏輯可能常常也是很複雜的。

舉例來說,假設小馬想要做一款雙人對戰的卡片對戰遊戲,
小馬創造一張卡片,
名稱叫作「俄羅斯輪盤」,
效果為「對任意角色造成傷害,此卡會重複施放十次。」
那麼每次判定的邏輯大概是長什麼樣子呢?
首先是卡片對戰的邏輯,
通常是打到其中一方人物生命值歸零,
遊戲就會結束,
這樣卡片效果可能就會在施放十次之前結束。

以下是小馬設計的效果判定的邏輯:

先施放卡片效果。
若我方生命值小於0,回傳"輸掉遊戲",否則
    若敵方生命值小於0,回傳"贏得遊戲",否則
        若卡片效果已施放十次,回傳"結束效果",否則
            回傳"繼續施放卡片效果"

這邊我們假設myHP, enemyHP, cnt, 都是可取得的全域變數,
分別代表「我方生命值」、「敵方生命值」、「卡片施放次數」,
小馬試著把它寫成程式邏輯:

化簡前:

def judge():
    if myHP <= 0:
        return "輸掉遊戲"
    else:
        if enemyHp <= 0 :
            return "贏得遊戲"
        else:
            if cnt == 10:
                return "結束效果"
            else:
                return "繼續施放卡片放果"

可以看到程式邏輯花了三層的if-else嵌套來做,
不是那麼好讀。

化簡後:

def judge():
    if myHP <= 0:
        return "輸掉遊戲"
    
    if enemyHp <= 0 :
        return "贏得遊戲"
    
    if cnt == 10:
        return "結束效果"
    
    return "繼續施放卡片放果"

把多層if-else拆開後,整支程式就乾淨好讀多了。

技巧5 – Python高手才知道的and與or 特性

這是python中還蠻特別的特性,
不一定每個程式語言都有。
在python中,and, or 進行的邏輯運算,
不一定總是返回True或是False這兩個值,
而是返回它們實際進行比較的值之一。
記住四個大原則,

  1. 元素除了0、空(如: 空字串、空列表、…)、None、False以外都當成是True。
  2. and 運算若全為真,返回最右邊的真值,否則返回第一個假值
  3. or 運算若全為假,返回最右邊的假值,否則返回第一個真值
  4. 若同時有and, or時,and會優先計算 (就像數學算式同時有加減乘除時,先乘除後加減的道理一樣)
    實際看一些例子會比較明白:
>>> 1 and 2 and 3
返回3 (全為真值,返回最右邊的真值)
>>> False or []
返回 [] (全為假值,返回最右邊的假值)
>>> '0' or 0
返回 '0' ('0'不是空字串,是真值)
>>> 1 and {} and False and 3
返回 {} (返回第一個假值)
>>> 1 or 0 and 2
返回 1 (原式相當於 1 or (0 and 2) )
>>> 0 or 1 and 2
返回 2 (原式相當於 0 or (1 and 2) )

若你第一次看到這種機制,
可能會覺得奇怪,
為何規則2和規則3是這樣運作呢?
關鍵在and, or 的判斷中,
常常不需要檢查到最後一個條件即知道結果
這邊小馬and, or 各上一個生活化的例子來說明,

and版本-優先返回第一個假值

假設媽媽下廚,
想要煮牛排給家人吃,
但是食材並不會自己從天上掉下來,
所以媽媽需要去超商買牛排肉。
媽媽會出門買牛排肉需要同時滿足下面三個條件:

  1. 今天是晴天 (因為媽媽覺得雨天出門穿雨衣很麻煩)
  2. 超商有營業
  3. 牛排肉特價20% (媽媽省吃儉用不喜歡買貴)
    邏輯可以寫成
if 今天是晴天 and 超商有營業 and 牛排肉特價20%:
    媽媽會出門買牛排肉

那麼如果第一個條件為假,
也就是今天下雨,
那麼已經可以確定媽媽是不會出門買肉的,
後面兩個條件根本不必檢查。

又或者今天雖然是晴天,
但是很不幸今天碰到國定假日,
超商沒有營業,
那麼牛排肉有沒有特價也就不必再檢查了,
這也就是為什麼and 總先返回第一個假值。

or版本-優先返回第一個真值

現今很多大學都設有英文畢業門檻,
意思是你必須任意選擇一種英文檢定考試通過,
才可以畢業。
假設有一間學校的規定需通過任一種測試方可通過英文畢業門檻:

  1. 參加全民英檢中級英文考試,通過初試
  2. 參加多益英文考試,得到700分以上
  3. 修習語言中心開設「進修英文」課程及格
    邏輯可以寫成
if 全民英檢中級初試通過 or 多益700分以上 or 通過校內進修英文課程:
    通過英文畢業門檻

那麼假設你已經通過全民英檢中級初試了,
學校便不必去檢查你有沒有考過多益英文考試,
或檢查你有沒有上過一門課叫做「進修英文」。
因為第一個條件滿足便確定通過門檻了,
這也就是為什麼or 總是返回第一個真值。

實例應用?

假設nums代表一個正整數的列表,
我們希望寫一個函數,
計算列表的數字總和。
若列表為空,則回傳-1。
直觀的寫法可能為

def sumOfList(nums):
    return sum(nums) if len(nums)!=0 else -1

若是and, or高級語法則長這樣:

def sumOfList(nums):
    return len(nums)!=0 and sum(nums) or -1

今天就先學到這裡囉。

課後練習

為了跟著小馬腳步一同學習的邦友,
這邊附上熱騰騰的練習題
歡迎於下面留言區討論你的想法哦。

習題一: 今天星期幾

寫一支程式,判斷使用者輸入的數字代表星期幾
(0: 星期日、 1:星期一、…、6: 星期六)
但這支程式似乎有些冗長,
你能利用本文教的技巧化簡嗎?

t = int(input())
if t == 0:
    print("今天星期日")
elif t == 1:
    print("今天星期一")
elif t == 2:
    print("今天星期二")
elif t == 3:
    print("今天星期三")
elif t == 4:
    print("今天星期四")
elif t == 5:
    print("今天星期五")
elif t == 6:
    print("今天星期六")

習題二: 判斷閏年

在曆法中,
若n是4的倍數但不是100的倍數,
或者n是400的倍數,
則n 為閏年。
小馬希望實作一個函數輸入數字n,
回傳n是否為閏年,
實作如下:

def isLeapYear(n):
    if n%4==0:
        if n%100!=0:
            return True
        else:
            if n%400 ==0:
                return True
            else:
                return False
    else:
        return False

只是目前這個函數邏輯太複雜,
會讓讀者看的頭昏腦脹。
你能夠想到最精簡的寫法為何呢?


2 則留言

1
Pondudu
iT邦新手 5 級 ‧ 2020-04-20 11:12:18

小馬哥安安,以下是我的解答:
習題一: 今天星期幾

t = int(input())
D = {0:"今天是星期日", 1:"今天是星期一", 2: "今天是星期二", 3:"今天是星期三", 4:"今天是星期四", 5:"今天是星期五", 6:"今天是星期六"}
print(D[t])

習題二: 判斷閏年

def isLeapYear(n):
    return n%4==0 and n%100!=0 or n%400==0

還有之前驗證變數名稱的練習題:

def isLegal(s):
    for word in "_":  
        ss = s.replace(word,"")  
        return ss.isalnum() if s[0].isupper() or s[0].islower() or s[0] == "_" else False

print(isLegal("aPPle"))  #True
print(isLegal( "_1abC"))  #True
print(isLegal("998Fg"))  #False
print(isLegal("@2fgh$"))  #False
print(isLegal("Bk89 ag"))  #False

忘了三元運算子語法的if和else要成對出現,偷看筆記才發現問題嘿嘿~
想請教小馬哥,或許有更簡潔的解法可以提示嗎,不確定有沒有運用到本課的精隨,再麻煩指正,感謝!!

嗨嗨,嘟嘟:
你真的很聰明呢~ 寫的蠻好的
之前驗證變數名稱的習題有更簡單的方式哦,
分享給你做參考:

def isLegal(s):
    return s.replace('_','').isalnum() and not s[0].isdigit()

首先,你的for迴圈for word in "_":,是可以拿掉的,因為"_"裡面也就只有一個字而己,因此可以簡單寫成s.replace('_',''),判斷說把_符號去除是不是只由英文和數字組成

再來是判斷首字不能是數字,
可以簡單寫成not s[0].isdigit()就好了,
s[0].isdigit()是判斷首字是數字,
如果要判斷「不是」的話,在前面加個not即可

同時滿足兩個條件就用and連起來

希望這個解答有給你一些啟發 ^^

Pondudu iT邦新手 5 級 ‧ 2020-04-20 13:44:05 檢舉

哇~整個變超簡潔XD 瞭解,反向思考會更簡單呢!又學到一課,謝謝小馬哥~

1
applebad5755383
iT邦新手 5 級 ‧ 2020-04-20 15:22:24

小馬哥安安 我是剛學習的新手 習題1我發現跟第一個回答者不一樣的寫法,不同的寫法是否有影響呢?以結果來看似乎都有達到要求

t = int(input())
S = ["Sunday","Monday","Tuesday","Wednesday ","Thursday","Friday","Saturday"]
print("Today is"+S[t%7])

您好,謝謝你的分享,用列表也是OK的哦,
只要能達到結果的方法都是好方法~

字典和列表的差別算是字典的用途更廣吧,
列表一定只能用index去拜訪元素,
但字典可以用整數或是字串甚至元組當「key」值去查找元素

謝謝小馬哥分享,讓我們學習真的受教了呢

不客氣,很高興幫到你 ^^

我要留言

立即登入留言