iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 8
2
Software Development

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

Day8- 第一屠龍刀- range函數與列表切片的融會貫通

路遙知碼力,日久練成精- 傳說「切片」與「列表生成式」是python語法世界中好用的屠龍寶刀,當真效果這麼好用嗎?讓我們繼續看下去。

大家好,我是心原一馬,內心原來一心喜歡打程式碼。
首先還是要先公佈昨天課後練習的解答:

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

不知各位讀者們有想出來嗎?

接下來終於要進入小馬認為python精簡語法最重要的兩課了-
「切片」(slice)「列表生成式」(List Comprehensions)
基本上,很多python精簡的語法,
能夠使多行程式碼變一行的絕招都是靠「切片」與「列表生成式」來實現的。

只要能精通這兩招,猶如畫龍點睛,
而其它python的高級語法,
小馬認為比較像是錦上添花了。

說「切片」與「列表生成式」就像是Python世界中的「屠龍寶刀」真是一點也不為過,
雖然簡單但又非常實用。
今天先將第一把刀傳授給你們吧。

切片介紹

假設我們有一個列表,表示早餐店的菜單,如下:

foodArr=["香雞堡", "豬肉堡", "牛肉堡", "總匯三明治", "鮪魚三明治", "火腿三明治"]

那假設我們想要取得前三個元素,可以怎麼做呢?
最普通的方法便是直接取值:

foods= [foodArr[0], foodArr[1], foodArr[2]] # ["香雞堡", "豬肉堡", "牛肉堡"]

可是假如陣列很大,
我們想要取的是前一百個元素,
這樣寫就不切實際了。

第二招是使用for迴圈,如下:

foods=[]
for i in range(3):
    foods.append(foodArr[i])

但是用for迴圈又稍嫌繁瑣,
python的切片操作符就是用來簡化這種操作而設的。

切片操作符的基礎語法:

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

其中L可以是列表或是字串或是元組。
L[起始位置(包含) : 結束位置(不包含): 步長] 的意思為,
從L這個列表,取出範圍為L[起始位置] ~ L[結束位置-1]之間的數字,
每步長個間隔取一次,
若步長這個參數省略則預設為1。
也就是說,上述取出前三個元素的動作,
可以簡單用切片來取:

foodArr=["香雞堡", "豬肉堡", "牛肉堡", "總匯三明治", "鮪魚三明治", "火腿三明治"]
foods= foodArr[0:3]

如果第一個參數是0,可以省略:

>>> foodArr[:3]
["香雞堡", "豬肉堡", "牛肉堡"]

若第二個參數省略,表示從起始位置直接取到列表的結束,
例如:

>>> foodArr[3:]
['總匯三明治', '鮪魚三明治', '火腿三明治']

因此,舉一反三一下,
第一個參數和第二個參數同時省略,
就可以得到整個列表的複製。

>>> foodArr[:]
['香雞堡', '豬肉堡', '牛肉堡', '總匯三明治', '鮪魚三明治', '火腿三明治']

在記得在Day3中我們對冰箱貼標籤的例子嗎?
切片運算的效果是,
將舊列表中取出一部分元素出來,
創造一個新的列表。
至此我們已經填了第一個坑,
也就是為什麼在範例3-3中的這段程式

fridge = ['蛋糕', '蘋果', '香蕉']
b = fridge[:]
b.remove('蛋糕')
print("列表b的內容為:", b)
print("列表fridge的內容為:", fridge)

(結果:
列表b的內容為: ['蘋果', '香蕉']
列表fridge的內容為: ['蛋糕', '蘋果', '香蕉'])

可以得到這樣的效果:
https://ithelp.ithome.com.tw/upload/images/20190929/20117114OzrlumzsO5.png

index也有負值?

這是在python中蠻特別的特性,
持以負數索引取值,
例如:

>>> foodArr[-1]
'火腿三明治'

foodArr[-n]可以便是取得列表的倒數第n個元素。

切片一樣也支持負數索引,例如取得倒數兩個元素:

>>> foodArr[-2:]
['鮪魚三明治', '火腿三明治']

我們試試更進階的吧,加入步長參數看看,
取得奇數項元素:

>>> foodArr[::2]
['香雞堡', '牛肉堡', '鮪魚三明治']

這邊把第一、二個參數都省略,
所以起始位置會從foodArr[0]開始,步長設為2,
所以會依序取得foodArr[2]、foodArr[4]的值,
直到列表的結束。

再舉一反三一下,
取得偶數項元素:

>>> foodArr[1::2]
['豬肉堡', '總匯三明治', '火腿三明治']

字串也要切片

字串切片的用法也是一樣的,例如我們有一個存放數字的字串:

numStr= "123456789"

同樣的,我們試試字串切片的操作。
取得末四碼:

>>> numStr[-4:]
'6789'

取得'23456':

>>> numStr[1:6]
'23456'

亦可以倒著取字,
從第八個字取到第六個字:

>>> numStr[7:4:-1]
' 876'

關於此例為什麼這樣寫可能一時無法反應過來,
這邊解說一下,
因為第一個字實際上是numStr[0],
故第八個字為numStr[7]; 第六個字為numStr[5];
記得切片第二個參數是不包括該數的,
故「結束位置」設為4,
又因為是倒著取的,步長設為-1。

有了切片運算符後,我們可以很輕鬆的取得一段連續資料了,
我們再來看看range()函數的用法。

range函數介紹

這邊把range函數跟切片放在同一篇文中介紹,
因為其實range的參數規則跟切片幾乎是相同的。

列表(字串)切片 v.s. range()

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

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

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

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

根本語法上就非常相似對不對?

並且,若步長這個參數省略則預設為1,
起始位置省略則預設為0。
例如:

range(5) 會生成 0,1,2,3,4
range(2,6) 會生成 2,3,4,5
range(4,21,3) 會生成 4,7,10,13,16,19

range本身是一個迭代器
迭代器是什麼東西後續篇章還會再細說,
現在先想成是可以用for迴圈遍歷的東西都叫迭代器即可。

如果我們直接嘗試用print把它印出來,
例如:

print(range(5))

結果會顯示range(0, 5)
那要如何把裡面的數字印出來呢?
方法一是用常見的for迴圈遍歷:

for i in range(5):
    print(i)

方法二是用list()將range()生成的數字轉換為list,例如

>>> list(range(5))
[0, 1, 2, 3, 4]
>>> list(range(2,6))
[2, 3, 4, 5]
>>> list(range(4,21,3))
[4, 7, 10, 13, 16, 19]

最後,我們看一個例子以了解range()的應用。

範例8-1: 等待女兒放假的老爸

有一對姐妹正值要出社會的年紀,
各自找到了一份工作,
姐姐的工作是每工作兩天可以放假一天,
妹妹的工作是每工作四天可以放假一天。
姐妹倆都是從九月一日開始第一天的工作。

老爸想要知道他的女兒什麼時候放假,
好安排家庭活動。
老爸需要一張當月女兒的工作排休表,
形式為一個長度為30的列表,如下:
[ 1, 2, '姐姐放假', 4, '妹妹放假', '姐姐放假', …]
如果該日姐妹都要工作,直接印出當日的日期;
如果該日只有妹妹放假,印出'妹妹放假';
如果該日只有姐姐放假,印出'姐姐放假';
如果該日姐妹倆都放假,印出'姐妹都放假'。

有了range()函數步長的概念,
我們可以不必寫一堆if判斷式,
直接找規律即可。
首先我們先宣告一個具有1~30數字的列表:

holidays= list(range(1,31))  #此時holidays=[1,2,3,…,30]

(相信讀者們讀到這邊應該已經了解為什麼是range(1,31),
而不是range(1,30)了)
然後再根據有放假的日子,更新列表的值。
例如姐姐是每隔三天一次休假,
初始值是「第三天」放假,
程式如下:

for i in range(2,30,3):
    holidays[i]="姐姐放假"

由於「第一天」,相當於是列表的index「0」,
故初始值設為「2」,步長為「3」。
此時列表原本位置的3,6,9,…,30都改為"姐姐放假"。

妹妹放假的邏輯類似,程式如下:

for i in range(4,30,5):
    holidays[i]="妹妹放假"

另外,還要設定「姐妹都放假」的日子,
國小有學過最小公倍數的概念,
可知姐妹每隔15天會共同休假:

for i in range(14,30,15):
    holidays[i]="姐妹都放假"

完整實作程式如下:

holidays= list(range(1,31))

for i in range(2,30,3):
    holidays[i]="姐姐放假"
    
for i in range(4,30,5):
    holidays[i]="妹妹放假"
    
for i in range(14,30,15):
    holidays[i]="姐妹都放假"
    
print(holidays) #印出結果

今天給大家介紹的切片和range()函數就講到這邊了。
接下來就是大家最期待的課後練習時間啦。

課後練習

聽說「每日一coding,痴呆遠離我」,
相信每天跟著小馬腳步一同思考的讀者們,
也漸漸能寫出一手漂亮的python程式碼哦。

習題: 我就是明日歌星

阿拉拉參加一個唱歌選拔賽,
總共有n個評審(至少有3個),
每個評審在聽完阿拉拉的歌曲後,
會給出1~10分的分數。

在賽制中,
為了更加客觀的評分,
給出最高分與最低分的評審分數會被去除,
僅留下其餘n-2個評審排序後的評分。
舉例來說,
假設n=10,
十位評審的評分為:

scores = [10, 10, 3, 10, 9, 10, 2, 10, 6, 5]

那麼10分與2分各自為最高分與最低分的分數,
因此去除這兩個分數後,
希望返回

[3, 5, 6, 9, 10, 10, 10, 10]

請實作函數judge(scores),

def judge(scores):
    return #這邊輸入你要回傳的值

參數scores是一個長度至少為3的數字列表,
表示n個評審的評分,
請回傳去除最高分與最低分的評審分數後的新列表。
(提示: 別被題目所敘述的去除誤導而想到列表的remove()函數了,
想想切片加上某個內建函數來解)


上一篇
Day7- 如果你願意一層一層一層 的剝開繁瑣邏輯 (化簡冗長的if-else教學)
下一篇
Day9- 中秋特輯: 月餅店攬客花招,random模組的運用
系列文
活用python- 路遙知碼力,日久練成精30

1 則留言

1
ccutmis
iT邦高手 4 級 ‧ 2019-09-12 09:39:08
return sorted(scores)[1:-1]

/images/emoticon/emoticon81.gif

心原一馬 iT邦研究生 5 級 ‧ 2019-09-12 10:54:04 檢舉

嗨,ccutmis 邦友你好,答案寫的很精簡呢,恭喜正解。
有看到你常常來關注小馬的文章,
感謝你欣賞我的文章,
小馬會繼續加油的。
/images/emoticon/emoticon41.gif

ccutmis iT邦高手 4 級 ‧ 2019-09-12 10:55:30 檢舉

/images/emoticon/emoticon12.gif 好文章一定要關注的呀

我要留言

立即登入留言