iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 13
3
Software Development

活用python- 路遙知碼力,日久練成精系列 第 13

Day13- 第二屠龍刀二式- 列表生成式(二) 搭配if使用達到過濾的效果

路遙知碼力,日久練成精-只要在程式之路鑽研的夠深,便能夠充分發揮程式碼的力量; 練習的日子夠久,便能夠練成寫出精簡代碼的能力。

首先公佈昨天課後練習解答:
(還沒看過題目的朋友歡迎點昨日題目傳送門)

def findMin(N, W):
    return min([min(w) for w in W])

min()函數無法直接取得二維list的最小值,
所以你可以這樣做,
用列表生成式取得每列list的最小值再取整體的最小值,
你答對了嗎?
(其實參數N是可以不用的,別被題目誤導囉)

搭配if使用達到過濾的效果

大家好,今天進入「列表生成式」語法第二招囉。
除了昨天說的可以單純用一個列表生成另一個新列表之外,
有時候我們會想要取出列表內滿足條件的元素,
而非取出所有元素,
這時可以直接在「列表生成式」後面加上if語句,
決定篩選的條件。
語法大致上如下:

新列表 = [something for x in 舊列表(或可迭代物件) if 條件判斷句] 

直接看幾個例子比較容易理解。

取出列表中的偶數

>>> L=[18, 73, -24, 6, 34, -87, -17, -98, -53, 16]
>>> [x for x in L if x%2==0]
[18, -24, 6, 34, -98, 16]

取出列表中的負數

>>> [x for x in L if x<0]
[-24, -87, -17, -98, -53]

取出列表中的字串型態

(註: 內建函數isinstance(x, type)可以判斷前者是否為後者的變數型態)

>>> M = ["Horse","Tiger",25,6.2,"Tomato"]
>>> [x for x in M if isinstance(x, str)]
['Horse', 'Tiger', 'Tomato']

複習: 三元運算子- 簡化的if-else 語句

還記得我們在Day7- 如果你願意一層一層一層 的剝開繁瑣邏輯這篇中教過如何化簡if-else嗎?
這邊快速的幫大家複習一下:
當我們用if-else語句時,
普通的寫法表達為:

if 條件成立:
   變數= 值1
else:
   變數= 值2

必須要寫四行,實在有點繁瑣。
從前人「一目十行」,
形容人的閱讀速度很快。
現代人「螢幕十行」,
形容電腦螢幕太小,
程式碼行數太多都要上下捲動螢幕才能看到完整的程式碼。

還好在python中可以將if-else濃縮成一行,
程式語法可以簡化為:

變數 = 值1 if 條件成立 else值2

舉例來說,當我們想要判斷使用者輸入的數字是否為3的倍數,
我們便可以這樣寫:

num = int(input())
print(f"{num} 是 3 的倍數" if num % 3 ==0 else f"{num} 不是 3 的倍數")

注意使用三元運算子語法時,if-else一定要成對出現
如果是加在列表生成式for迴圈後面的if語句則不會有else出現

緊接著看幾個應用幫助了解使用情境吧。

範例13-1 阿明買蛋糕

阿明的女朋友生日要到了,
阿明決定到蛋糕店買兩個小蛋糕給女朋友慶生,
給定一個陣列表示所有可以買的蛋糕價格。
為了表達滿滿的心意。

阿明希望買的蛋糕總價格愈高愈好,
但由於阿明還是學生,打工收入不高,預算有限。
請幫阿明計算他在不超過預算的前提下,
可以買到的兩個小蛋糕價錢最高為何?

請實作函數 mostValue(nums, K)
參數nums代表所有可以購買的蛋糕價錢(一定全是正整數),
參數K 是一個正整數代表阿明的預算。
回傳陣列兩個數的最大總和但要小於K。
(如果買不起蛋糕則回傳 -1)

例子1:

nums = [100,200,500,2000]
K = 1000
Output: 700

例子1表示店內有賣100,200,500,2000元的蛋糕各一個,
但阿明預算只有1000元,
故最高只能買200+500=700元的蛋糕。
(注意不能買2個500元的蛋糕,因為500元的蛋糕只有一個。)

例子2:

nums = [100,200,500,2000]
K = 200
Output: -1

例子2表示店內有賣100,200,500,2000元的蛋糕各一個,
但阿明預算只有200元,
阿明是買不起兩個蛋糕的,故函數應回傳 -1。

函數實作如下:

def mostValue(nums, K):
    L=[nums[i]+nums[j] for i in range(len(nums)) for j in range(i+1,len(nums)) if nums[i]+nums[j]<=K]
    return max(L) if len(L)>0 else -1

函數內第一行表示窮舉陣列所有相異索引(index)的兩個數相加,
並過濾出所有不超出預算的金額。
第二行運用了三元運算子,
如果L不為空陣列(即阿明買的起兩個蛋糕),
回傳最高的金額,
否則回傳 -1。

利用以下程式進行測試:
測試程式如下:

nums = [100,200,500,2000]
print(mostValue(nums, 1000))
print(mostValue(nums, 200))

結果印出
700
-1
是我們要的預期結果。

範例13-2 聽說好課值得一修再修?

給定一個列表表示班上同學的分數,
例如:

scores = [20,30,50,60,25,70]

希望把每個人的分數換算成是否及格的結果,
期望結果: ['需要重修', '需要重修', '需要重修', '及格', '需要重修', '及格']
若分數在60分以上,換成「及格」字串,
否則換成「需要重修」字串。

其實這是三元運算子在列表生成式中的運用,
程式如下:

scores = [20,30,50,60,25,70]
passed = ["及格" if s>=60 else "需要重修" for s in scores]
print(passed) #['需要重修', '需要重修', '需要重修', '及格', '需要重修', '及格']

範例13-3 判斷質數

質數(prime) 是只能被1和自己兩個數整除的正整數 (注意1不算質數)。
判斷一個數是否是質數是數學上的經典問題,
也是程式設計課常常拿來做為入門的一個問題。
但大概少有程式語言能寫的像python一樣精簡的吧?
我們希望實作一個判斷質數的函數:

def isPrime(n):
    pass

輸入n是一個正整數,
如果n是質數,函數回傳True;
如果n不是質數,函數回傳False

目前小馬有想到兩種簡潔的語法來做,
各自練習到在列表生成式後加上if判斷條件,
以及三元運算子應用在列表中兩種情況,
就看大家比較喜歡哪一種囉。

解法一:

第一種方法,
可以檢查所有在1~n的數字,
如果該數字可以整除n的話,
就存在列表中。
例如6的因數有1,2,3,6,
我們希望得到一張列表[1,2,3,6]。
由於質數表示恰好有兩個因數,
只要檢查這個列表是否恰有兩個元素即可。
程式碼如下:

def isPrime(n):
    return len([i for i in range(1,n+1) if n%i==0])==2

解法二:

解法二同樣去檢查所有在1~n的數字,
但是我們存一張長度為n的列表,
如果該數字可以整除n的話,
列表的對應位置為1,否則為0。
例如6的因數有1,2,3,6,
我們希望得到一張列表[1,1,1,0,0,1],
表示6可以被1, 2, 3, 6整除,
但不能被4, 5整除。
只要檢查這個列表的總和是否為2,
即知道n是否恰好有兩個因數。
程式碼如下:

def isPrime(n):
    return sum([1 if n%i==0 else 0 for i in range(1,n+1)])==2

可以簡單測試一下程式:

for i in range(1,10):
    print(f'{i}是質數?: {isPrime(i)}')

結果為:

1是質數?: False
2是質數?: True
3是質數?: True
4是質數?: False
5是質數?: True
6是質數?: False
7是質數?: True
8是質數?: False
9是質數?: False

結果正確。
但是目前解法一,二都有個待改善的問題: 效率不佳。
譬如說你在你的電腦上用這兩個解法測一下10的8次方(即一億)是否為質數:

print(isPrime(10 ** 8))

估計沒有跑個好幾秒應該是跑不出結果的。
(時間因電腦效能而異,跑個好幾十秒可能也是正常的)

為什麼我們認為算法效率不佳呢?
因為一億這個數是一個偶數呀,
當我們檢查到一億可以被2整除時,
一億就不可能是質數了,
接下來是不需要再把3到一億這麼大量的數檢查完才能判斷的。

那麼如何提升判斷質數的效率呢?
除了你可能想的到的用while-break語法的語法來解,
在檢查到一億可以被2整除時提早跳出迴圈。
我們還有python風格的語法,
同時兼顧執行效率又維持精簡的程式,
預計在後續篇章談到any(), all()函數時會探討到,敬請期待。

少年啊,我將最精華的兩把python屠龍刀都傳給你了,多看多實作吧

至此為止,小馬已經把認為最能代表python特色的語法-
「切片」與「列表生成式」傳授給各位了,
對這兩個語法熟悉,
相同功能要寫出僅約c/c++, java三分之一到五分之一的代碼量也不是問題。

但是光學語法而沒有練習是不會進步的,
這樣會只停留在「知道」有這個語法,
實際解題還是不知道怎麼使用。

平時練習不必追求一步到位,
不必想說解題就一次想到最佳解法才下筆,
但可以擺脫初學的思維「只要能夠讓程式動起來」就好,
時時自問這隻程式有沒有更好的寫法。

小馬預計在鐵人賽後半,
會帶來更多進階技巧及實際解一些小專題,
讓大家看看python漂亮的語法究竟能實作出什麼呢?敬請期待

課後習題- 韓信的千軍萬馬

底下的習題很簡單,
題目改自自己寫過的一篇python基礎教材
或許你沒學過python而用你原本會的語言都解的出來,
不過試試用python簡潔的風格做出來吧。
歡迎於留言區討論想法,解答於次日公佈。

韓信點兵」是經典的數學問題,題意是說
韓信讓他的士兵三個排成一列會餘2個;
士兵每五個排成一列會餘3個;
士兵每七個排成一列會餘2個。
已知目測韓信的士兵大於m人,小於n人,
那麼韓信可能有多少士兵呢?

請實作以下函數:

def soldiers(m, n):
    return #這邊寫你要回傳的值

返回一個列表,表示韓信可能的士兵數量。

例如:

輸入: m = 20, n=200
輸出: [23, 128]

上一篇
Day12- 第二屠龍刀一式- 列表生成式(一) 精簡的語法,利用一個列表生出另一個
下一篇
Day14- 理解Python的生成器與迭代器; 高階函數map, filter, reduce 秀一波,竟有兩個好像似曾相識?
系列文
活用python- 路遙知碼力,日久練成精30

尚未有邦友留言

立即登入留言