iT邦幫忙

2

【python入門教室】(7) python 的列表生成式語法介紹

python是一門精簡的語言,
如果你對python很熟悉的話,
常常可以達到別的程式語言要打三行程式,
到了python只需要打一行

上一篇說到python中有兩個跟其它語言蠻不一樣的語法-
「切片」(slice)與「列表生成式」(List Comprehensions)

上回講完「切片」(slice)語法,
今天繼續講講「列表生成式」(List Comprehensions)的語法

列表生成式(List Comprehensions),顧名思義
,為運用一個列表生出另一個列表的語法
基礎語法如下:

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

相當於

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

這樣的寫法。 (註: append()是在列表後面添加新元素的意思)

在列表生成式語法中,
直接把for迴圈放進列表中以簡化程式碼

另外,有時候我們會想要取出列表內滿足條件的元素,
而非取出所有元素,
這時可以直接在「列表生成式」後面加上if語句,
決定篩選的條件。
語法大致上如下:

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

來看幾個簡單的例子

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

生成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']

取出列表中的偶數

>>> 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']

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

範例一: 救救我的期末分數啊

某高中數學老師,因為班上期末考的平均分數過低,
決定幫同學們調分。
調分方式為將班上最高分的同學加到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代碼,
卻又不失可讀性。

範例二: 各位數字和

小明上數學課,
學到了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

切片v.s. 列表生成式

切片與列表生成式都可以在python中從一個列表生出另外一個列表,
你會發現切片與列表生成式的語法還蠻像的,
其實也可以一起記 (可與前一篇【python入門教室】(6) python 的切片語法介紹的文章尾端「切片 v.s. range函數」一起看)

切片操作符的基礎語法為:

newArr = L[起始位置(包含) : 結束位置(不包含): 步長]

range()函數的基礎語法為:

range(起始位置(包含), 結束位置(不包含), 步長)

列表生成式的基礎語法為:

新列表 = [something for x in 舊列表(或可迭代物件,常常使用range)] 

如果有一個列表L(長度>21),我們想要從第四個元素開始,
每三個取一次,直到第21個元素停下來,
那可以怎麼寫呢?(即想要得到新的列表是[L[4], L[7], L[10], L[13], L[16], L[19]])
這個例子下,切片與列表生成式都可以用,
而且語法上還蠻像的

例子
range(4,21,3) 表示數字範圍 4,7,10,13,16,19
L[4:21:3] 則表示切片的列表 [L[4], L[7], L[10], L[13], L[16], L[19]]
[L[i] for i in range(4,21,3)] 亦可以表示[L[4], L[7], L[10], L[13], L[16], L[19]]

課後練習

  1. 7kyu- Sum of a sequence -將範圍內的數字加總,大家可以想想怎麼寫較簡單

2. 韓信的千軍萬馬

此題改編自小馬基礎教材【Python 超入門】(8) break/continue 邏輯- 造謠者向其它求職者說:「我已經拿到這份工作了,你們可以回家了」的題目

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

請實作以下函數:

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

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

例如:

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

1 則留言

1
Pondudu
iT邦新手 5 級 ‧ 2020-04-17 15:29:11

小馬哥安安,我的解答如下:

def soldiers(m, n):
    return [x for x in range(m,n) if x%3 == 2 and x%5 == 3 and x%7 == 2]

print(soldiers(20,200))  #[23,128]

在練習時給自己出題,為了印出[1, 4, 7, 10, 13]試出了5種解法,但是在練習字串轉列表給自己出題打出['1', '4', '7', '10', '13']時卡住了,最後打成

L = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"
N = L.split(",")
print([N[i] for i in range(0,14,3)])

才能印出我想要的結果,但就一定要打出L="1,2...,15"這個字串才行,本來想法是先用for迴圈打出[1, 4, 7, 10, 13]的列表再把整數轉成字串,但都失敗,想請問我的問題出在哪裡~''~?

看更多先前的回應...收起先前的回應...
Pondudu iT邦新手 5 級 ‧ 2020-04-17 15:48:19 檢舉

忘了附CodeWar的解答XD~(之前的也都忘記附,通過就關了XDD)

def sequence_sum(begin_number, end_number, step):
    return sum([i for i in range(begin_number, end_number+1, step)])

一開始沒注意到要加最後一個數字,後來改了end_number+1就成功了!

心原一馬 iT邦研究生 5 級 ‧ 2020-04-17 15:50:40 檢舉

嗨嗨,解答寫的很好哦,
不過如果要仔細一點的話,
因為題目要「大於m人」,嚴格來說範圍會從m+1開始,
解答如下:

def soldiers(m, n):
    return [x for x in range(m+1,n) if x%3 == 2 and x%5 == 3 and x%7 == 2]

你真的很認真的在練習呢,
為了印出[1, 4, 7, 10, 13]試出了5種解法(有點好奇你想到哪5種耶?),
精神可嘉

要印出['1', '4', '7', '10', '13']也有蠻多種方法的,
這邊給出小馬覺得簡單的一種:

nums = [str(i) for i in range(1,14,3)]
print(nums)

在列表生成式裡面,用str(i)將數字轉成字串,
這樣印出來的就是字串了,
不知道有沒有幫你解到惑,哈哈~

或者,你也可以貼出你嘗試失敗的程式碼,
小馬幫你看看哦~

Pondudu iT邦新手 5 級 ‧ 2020-04-17 16:26:46 檢舉

小馬哥好,原來如此!我太粗心啦XDD 感謝糾正!!

五種沒有差很多,其實就是小馬哥教過的啦XD:

#1
L = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
newList = []
for i in range(0,14,3):
    newList.append(L[i])
print(newList)

#2
newList = []
for i in range(1,15,3):
    newList += [i]
print(newList)

#3
L = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
print(L[0:14:3])  #切片語法

#4
newlist = [i for i in range(1,15,3)]  #列表生成語法
print(newlist)

#5
L = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
newlist = [L[i] for i in range(0,14,3)]  #列表生成語法
print(newlist)

原來那麼簡單!!那個錯誤版本我打成:

for i in range(1,15,3):
    n = list(str(i)) 
print(n)

結果不知道為甚麼印出['1', '3'],再麻煩小馬哥幫我看看了/images/emoticon/emoticon46.gif

心原一馬 iT邦研究生 5 級 ‧ 2020-04-17 18:30:07 檢舉

讚哦,你真是創造力十足呢,
溫故知新打了五種解法/images/emoticon/emoticon12.gif

關於你的程式,你可以在for迴圈裡印出一些資訊,
應該就一目瞭然程式發生什麼事了,比如這樣:

for i in range(1,15,3):
    n = list(str(i))
    print(str(i), list(str(i)))
print(n)

如果把一個字串用list()轉換成列表,
它的效果是把字串的每個字當成陣列的一個元素,
例如:

>>> list("123")
['1', '2', '3']

因此你看到的['1', '3']是直接把字串"13"轉成列表得到的哦~

Pondudu iT邦新手 5 級 ‧ 2020-04-18 08:14:38 檢舉

恩~好像懂了,但又有點不懂~"~ 所以就數字而言,只有列表生成語法可以直接將str()後的數字各自保留原本的樣子轉換成一個列表,list()卻會強制猜成一個一個(0~9的數字),我的理解這樣對嗎>.<

n = [str(i) for i in range(1,15,3)]

for i in range(1,15,3):
    n = list(str(i))

乍看之下超像的呢~XD 有比較瞭解差異了,謝謝!!

心原一馬 iT邦研究生 5 級 ‧ 2020-04-18 11:35:01 檢舉

嗨嗨,大概是這樣~

  • str(數字)可以將數字保留原本的樣字轉成字串
  • list(字串)會把字串轉換成一個一個的字元
  • 把str(i)放在列表生成式裡並沒有把字串轉換成列表的動作,它就是把字串放在列表裡面

寫程式的時候可以多思考,
我這樣寫會得到什麼結果呢?
若不確定的話就可以實際將變數印出來試試,
看看跟自己預期的結果是否相同,
相信多寫會愈來愈有感覺的 ^^

Pondudu iT邦新手 5 級 ‧ 2020-04-18 17:40:38 檢舉

好的! 謝謝小馬哥/images/emoticon/emoticon41.gif

我要留言

立即登入留言