iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 12
3
Software Development

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

Day12- 第二屠龍刀一式- 列表生成式(一) 精簡的語法,利用一個列表生出另一個

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

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

def checkRepeat(plays):
    return len(set(plays))<len(plays)

想法就是說如果去除重複元素之後,
原始列表的元素有變少,
那麼原始列表就真的有重複元素,
你有答對嗎?
有其它解法也歡迎在留言區分享哦。

第二屠龍刀- 列表生成式

大家好,我是心原一馬。
之前小馬比喻說「切片」與「列表生成式」就像是Python世界中的「屠龍寶刀」,
雖然簡單但又非常實用,
今天終於要傳授第二屠龍刀-列表生成式了。
列表生成式(List Comprehensions),顧名思義
,為運用一個列表生出另一個列表的語法。
基礎語法如下:

新列表 = [something for x in 舊列表(或可迭代物件)] 

相當於

新列表 = []
for x in 舊列表(或可迭代物件):
    新列表.append(something)

這樣的寫法。

列表生成式語法中,
直接把for迴圈放進列表中以簡化程式碼,
我們直接看一個情境了解列表生成式如何使用。

範例12-1 救救我的期末分數啊

某高中數學老師,因為班上期末考的平均分數過低,
決定幫同學們調分。
調分方式為將班上最高分的同學加到100分,
再將其它同學加上等量的分數。
例如有兩位同學分別考了30分及45分,
而全班最高分的同學考了80分。
老師便會將最高分的同學加20分成為100分,
再把30分及45分的同學各自加上20分,
變成50分及65分。
給定一個列表表示班上同學的原始分數,
例如:

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

計算出班上調分之後的結果。

不用列表生成式怎麼解

我們先思考一下如果不用列表生成式可以怎麼解。
首先我們先宣告一個新的列表用來存放新的分數:

new_scores = []

再來,我們用一個變數adjust記錄需要往上加多少分,
調分 = 100 - 全班最高分:

adjust = 100 - max(scores)

我們用for迴圈遍歷,
將原始分數加上調分後,
再依序放到新列表中,
完整程式碼如下:

scores = [20,30,50,60,25,70]
new_scores = []
adjust = 100 - max(scores)
for s in scores:
    new_scores.append(s+adjust)
print(new_scores)

結果為[50, 60, 80, 90, 55, 100]

列表生成式怎麼解

但是我們可以使用列表生成式,
把for迴圈放進列表中,
讓程式碼加精簡:

scores = [20,30,50,60,25,70]
new_scores = [s+100 - max(scores) for s in scores]

一行就解決了所有事情,
使用列表生成式的話,
可以大大簡化python代碼,
卻又不失可讀性。

看看列表生成式的各種應用

生成1~10的平方數

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

將字串轉為字元陣列

>>> s="Hello"
>>> [c for c in s] #亦可直接寫 list(s)
['H', 'e', 'l', 'l', 'o']

雙for迴圈生成全排列

>>> [m + n for m in 'abc' for n in '123']
['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']

簡單吧?
凡是運用可迭代容器的原始資料(如: 字串,元組,列表,字典),
要生成一個新列表,
都很適合使用列表生成式。

範例12-2: 各位數字和

小明上數學課,
學到了3的倍數判別法,
把一個整數的各個位數和相加,
如果是3的倍數的話,
原來的數也是3的倍數。
例如: 123的各位數字和為 1+2+3=6,
6是3的倍數,所以123也是3的倍數。

由於小明覺得這個判別法實在太神奇了,
因此之後小明看到一個整數,
都會有把整數的各個位數加起來的衝動。
請實作這個函數計算整數的各個位數和:

def sumOfDigit(num):
    pass

輸入num是一個整數 (為了方便,假設一定是正整數),
回傳該整數的各位數字和。

解法一: 單純把輸入當作數字操作

想一想,如果單純把num當作數字來操作的話,
要怎麼取得各位數字呢?
好像不是那麼好想。
先想如何取得「個位數字」吧。
數字除以10的餘數即是個位數字。
接下來要取得十位數字,
只要先把數字「向右移一位」再取個位數字即可。
亦即反覆的做對10取餘數、除以10的操作即可。
以 123 這個數字為例:

123 % 10 = 3
123 // 10 = 12
12 % 10 = 2
12 //10 = 1
1 % 10 = 1
1 // 10 =0

把過程中取餘數運算(%) 得到的數 3,2,1相加,
即為各位數字之和。
程式碼如下:

def sumOfDigit(num):
    ans=0
    while num>0:
        ans+=num%10
        num//=10
    return ans

解法二: 把輸入當作字串來處理

昨天為大家介紹python中各種不同資料型態的特性,
如果我們將數字想成是一個字串陣列呢?
例如把123的「1」,「2」,「3」都看做是一個字元,
直接把每個字加起來不是很直覺嗎?
我們搭配列表生成式,把數字字元轉換成數字,
程式碼如下:

def sumOfDigit(num):
    return sum([int(c) for c in str(num)])

舉幾個數字測試一下結果:

print(sumOfDigit(120)) #3
print(sumOfDigit(35)) #8
print(sumOfDigit(261)) #9
print(sumOfDigit(999)) #27

範例12-3: 九九乘法表

相信小時候大家都有背過九九乘法表吧?

1*1=1, 1*2=2, 1*3=3, …, 1*9=9,
2*1=2, 2*2=4, 2*3=6, …, 2*9=18,
…
9*1=9, 9*2=18,9*3=27, …, 9*9=81

試著用程式印出一份九九乘法表吧。

要直接印出整張九九乘法表似乎不好想,
如果我們只要印出一列呢?例如印出
3*1 到 3*9 的結果:

>>> [3*i for i in range(1,10)]
[3, 6, 9, 12, 15, 18, 21, 24, 27]

很簡單就可以寫出來。
但由於我們希望把整個算式3*1=3 印出來,
而不是只有印出3一個數,
我們需要使用字串前綴f來處理:

>>> [ f"3*{i}={3*i}" for i in range(1,10)]
['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']

(由於螢幕空間不夠印出一整列,印出來會換行顯示)
之前在Day9給大家講過一個很簡便的語法,
在字串前面加上前綴f
即可很方便的在字串裡面放入變數。
這是在python3.6版本加入的新語法,
在字串前面加上f,
f"3*{i}={3*i}"變會把大括號裡的數當作算式把值計算出來。
單純用字串串接的寫法 "3*"+str(i)+"="+str(3*i) 也可以得到一樣的結果,
但是當變數在字串裡面很分散的話,
用前綴f的寫法看起來會簡單很多。

既然會印出3*1 到 3*9 的結果了,
那如果改成印4*1 到 4*9 的結果,
也就只是簡單的舉一反三了:
[ f"4*{i}={4*i}" for i in range(1,10)]
也就是說,印出整張九九乘法表,
把原來放3的這個位置用個變數取代就行了。

>>> [[ f"{j}*{i}={j*i}" for i in range(1,10)] for j in range(1,10)]
[['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'],
…(中間省略部分結果)
 ['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']]

但是目前有個缺點,
全部都換行顯示,
並不是很方便閱讀,
我們可以搭配在Day6講解過的' '.join()語法,把列表內字串串起來:

for j in range(1,10):
    print(' '.join([ f"{j}*{i}={j*i}" for i in range(1,10)]))

結果為:

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

但是因為計算結果有的是一位數,有的是兩位數,
這樣印出來沒有對齊,
我們可以把列印的語法改成f"{j}*{i}={j*i:2d}"
在大括號中加入冒號,指定我們想要的格式,
例如{j*i:2d}表示把計算的結果當作2位數整數來印,
(你可以參考官方文檔對輸出格式的說明)

修改如下:

for j in range(1,10):
    print(' '.join([ f"{j}*{i}={j*i:2d}" for i in range(1,10)]))

結果為:

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

哇,沒想到短短程式碼竟可以這麼整齊的印出九九乘法表呢。
今天就先講到這裡吧,明天會繼續講講列表生成式更進階的運用。

課後練習

又到了每日一次的課後練習時間了,
底下的習題很簡單,
或許你沒學過python而用你原本會的語言都解的出來,
不過試試用python簡潔的風格做出來吧,
歡迎於留言區討論想法,解答於次日公佈。

習題: 小櫻的自訂鐵人賽

小馬參加iT邦鐵人賽。
小櫻是一位小說家,她覺得每天堅持做一件事很有意義,
也決定參加自訂「鐵人賽」,
她給自己的目標是連續N週每天創作小說。

用一個大小為N列七個元素的整數二維列表W
記錄小櫻每天創作的字數。

請實作findMin(N, W)這個函數,
回傳小櫻每天最少創作了多少個字。

以下給兩個範例:

範例一:
input: N=1,W=[[819, 958, 508, 969, 565, 800, 703]]
output: 508 (7個整數中最小的是508)

範例二:
input: N=2, W=[[740, 516, 725, 718, 861, 634, 723],
               [914, 747, 580, 593, 722, 877, 595]]
output: 516 (14個整數中最小的是516)

提示: 其實這個問題就是要求一個二維陣列的最小值,
如果如下直接呼叫內建函數min()可以嗎?

def findMin(N, W):
    return min(W)

不行的話,思考在列表生成式用min()?


上一篇
Day11- 讓我們優雅泡咖啡般地選擇容器吧,認識int(), float(), str(), list(), tuple(), dict(), set()
下一篇
Day13- 第二屠龍刀二式- 列表生成式(二) 搭配if使用達到過濾的效果
系列文
活用python- 路遙知碼力,日久練成精30

1 則留言

1
ovenchang
iT邦新手 5 級 ‧ 2020-04-17 17:08:12
def findMin(N, W):
    return min([a for ls in W for a in ls]))

我要留言

立即登入留言