串列
簡介
前面介紹到的資料型態,像是整數、浮點數、字串或是布林值,都是比較簡單的資料型態,除了這種比較單純的值之外,在 Python 有好幾種「集合」或「容器」型的資料結構,通常這些型式的資料結構都可以用來存放很多資料,在 Python 裡最常 用的一種叫做「串列(List)」。串列是一塊連續的記憶體位置,你可以把這一堆連續的記憶體想像一個一個小格子,每個格子成有隔板隔開,有點像用來放藥的藥盒子,裡面可以放各式各樣的東西:
Photo by Laurynas
在介紹串列之前,大家有沒想過為什麼需要這種東西?一般的數字或字串不夠用嗎?在一般的場合是很夠用的,不過如果想要一口氣把一堆相同或類似的資料放在同一個變數裡,或是想把一堆資料傳給另一個函數的時候,這時候使用這種集合型的資料型態就會比較方便了。
在 Python 的串列寫起來像這樣:
heroes = ["悟空", "鳴人", "魯夫"]
score = [10, 30, 50]
串列的外層是一對中括號,裡面的東西通常會稱它為「元素(Element)」,每個元素之間使用逗號分開。看到這裡,如果各位曾經接觸過其他程式語言,大概會認為「這不就是『陣列(Array)』嗎?怎麼到了 Python 就改叫串列了?」
若以使用的手感來看,串列的確是很像陣列沒錯,你也可以把它當成陣列基本上不會出什麼亂子,甚至已經寫了一陣子的 Python 工程師朋友也不一定能講出這之間有什麼差別。它們雖然形狀看起來很像、用起來也很像,不過在本質上還是有些差異,並不是只有稱呼不同而已,在本章最後的冷知識會 有比較詳細的補充說明。
陣列跟串列同樣都是一塊連續的記憶體位置,某些程式語言的陣列(例如 C 語言或 Rust),裡面存放的東西規定必須要是相同型別,要放字串就全部都放字串,要放數字就只能放數字,甚至整數跟浮點數還不能混著放,有的更嚴格的甚至是陣列一經宣告就不能再增加元素的也有。而在 Python 的串列沒有管這麼嚴格,串列裡你想放什麼都可以:
heroes = ["悟空", "鳴人", "魯夫"]
something = [1, True, None, 3.14, "python", heroes, [1, 2, 3]]
想放字串、數字、布林值都行,放個空值 None
也行,或是在串列裡面再放一層串列都沒問題,想怎麼放就怎麼放。
串列常見操作
有幾個元素?
先從最簡單的操作開始,先來試著印出串列裡有多少顆元素,Python 有個內建函數 len()
可以做這件事:
>>> heroes = ["悟空", "鳴人", "魯夫", "流川楓", "芙莉蓮"]
>>> len(heroes)
5
雖然 len()
函數字面上的意思是「長 度(length)」,但它算出來的結果就是這個陣列有幾顆元素。事實上,不只是串列,在 Python 只要是可以一個一個把東西拿出來的東西,都能使用 len()
函數取得個數,例如前面介紹過的字串,以及後面章節會介紹的字典、Tuple 跟集合也都能用。
索引值
接下來,如果想要取出串列裡的某一個元素,使用中括號搭配「索引值(Index)」就能取得指定元素。在 Python 的串列索引值是從 0 開始算,例如我想要印出「悟空」的話,它在這個 heroes
裡是第一顆元素,它的索引值就是 0。同理,在串列裡「魯夫」是第三顆元素,它的索引值就是 2。我們可以把它們印出來試試看:
>>> heroes = ["悟空", "鳴人", "魯夫", "流川楓", "芙莉蓮"]
>>> heroes[0]
'悟空'
>>> heroes[2]
'魯夫'
如果想要印出最後一個「芙莉蓮」的話呢?因為它是最後一個,掐指一算就會知道它的索引值是 4,所以使用 heroes[4]
就行了。這樣寫是沒問題,但如果遇到串列比較長一點的,算起來就有點麻煩。在 Python 有更簡單的寫法,就是使用負的索引值。負的索引值可以從後面算回來,例如 -1
就會取得這個串列的最後一顆元素,同理可證,索引值 -2
就會取得「流川楓」:
>>> heroes[-1]
'芙莉蓮'
>>> heroes[-2]
'流川楓'
那如果使用超過範圍的索引值呢?像這樣:
print(heroes[1450]) # 會印出什麼?
很明顯這裡並沒有這麼多元素,所以執行之後就 得到 IndexError
錯誤訊息,跟你抱怨你給的索引值超過範圍了:
IndexError: list index out of range
使用索引值不只可以從串列裡取得元素,也可以把指定位置的元素換掉:
>>> heroes = ["悟空", "鳴人", "魯夫", "流川楓", "芙莉蓮"]
>>> heroes[0] = "JoJo"
>>> heroes[-1] = "DIO"
>>> heroes
['JoJo', '鳴人', '魯夫', '流川楓', 'DIO']
上面這段程式碼意思,就是分別把第一個元素「悟空」換成「JoJo」,並且把最後一個元素「芙莉蓮」換成「DIO」。
巢狀串列
所謂的巢狀串列(Nested List)就是指串列裡又包了其他的串列,大概就是大腸包小腸的概念,看起來可能會像這樣:
heroes = [["悟空", "達爾"], ["鳴人", "佐助"], ["櫻木花道", "流川楓"]]
為什麼要在串列裡又包串列搞這麼複雜?通常是因為想把一些相 關的資料放在一起,使用巢狀串列就會相對的更簡單、更有組織性一點。要取得巢狀串列裡的元素,也是透過索引值的方式,只是要再多一層 []
。例如我想印出在第 2 個元素裡的第 1 個元素「鳴人」:
>>> heroes[1][0]
'鳴人'
如果想要把整個巢狀串列裡的元素都印出來的話,用兩層迴圈就能做到:
heroes = [["悟空", "達爾"], ["鳴人", "佐助"], ["櫻木花道", "流川楓"]]
for hero in heroes:
for character in hero:
print(character)
這樣就搞定了。
《冷知識》為什麼索引值從 0 開始?
有沒有想過為什麼我們從小學習的數學是從 1 開始算,但好像大部份比較主流的程式語言的陣列包括 Python 的串列,索引值卻是從 0 開始算?因為這是因為陣列的索引值並不是指向元素本身,比較像是記憶體位置的「偏移量」。假設有一個陣列像這樣:
chars = ['a', 'b', 'c', 'd']
當我們說「變數 chars
指向 ['a', 'b', 'c', 'd']
」的時候,並不是 chars
這個變數直接指向這一整個陣列,而是指向這個陣列的「起始位置」,更精準的說是記憶體位置。陣列在記憶體裡的位置跟門牌號碼一樣都是連號的,假設這個陣列的起始記憶體位置是 1450
,然後每個元素所佔的位置或說格子的大小我先隨便假設是 8(先不用管它的單位以及為什麼是 8)。大概示意圖如下:
這樣的話,你認為要如何取得第一個元素的 a
字元?不就是指向記憶體位置 1450
就拿到了嗎?如果知道怎麼拿第一個元素,要怎麼拿第二個、第三個元素?這簡單,因為每個格子的寬度是固定的,所以只要知道第一個元素的位置再搭配簡單的加法跟乘法就能算出來了:
第 2 個元素 = 1450 + (1 x 8) = 1458
第 3 個元素 = 1450 + (2 x 8) = 1466
依此類推,所以我們就能推導出第 N 個元素的計算公式:
第 N 個元素 = 起始位置 + ((N - 1) x 8)
到這裡,大家可能也有發現上面公式裡的 N - 1
,剛好就是我們平常在講的索引值。所以索引值其實就是記憶體位置的「偏移量」,因此才會從 0 開始。
陣列索引值從 0 開始算的概念是從滿早期的程式語言開始就有了,大家用著用著也習慣了,Python 以及其他程式語言也都受到影響,所以 Python 的串列索引值也跟著從 0 開始算。
但並不是所有程式語言的索引值都是從 0 開始算,不同程式語言對這件事有不同的觀點,像程式語言 Lua、Fortran、COBOL、Julia 跟 R 語言等都是從 1 開始算的,也許在它們的觀點來看,chars[1]
就是第一個字或第一個欄位,看起來更簡單、直覺。
元素是否存在?
要如何知道某個元素是不是有在這個串列裡呢?有幾種做法,比較簡單又直覺的寫法,就是使用 in
關鍵字:
>>> heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
>>> "悟空" in heroes
True
>>> "芙莉蓮" in heroes
False
不管這個元素是否存在串列中,in
關鍵字一定會給一個布林值的答案,不會無聲卡。這很常在進行邏輯判斷的時候用到,例如:
heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
if "悟空" in heroes:
print("悟空在家")
else:
print("哥哥不在家,今天不賣酒!")
有沒有發現這語法還滿直覺的?就算你不會寫程式,只要略懂英文就大 概能猜的出意思,這也是 Python 這個程式語言的優點之一。除了 in
關鍵字之外,還可以透過串列本身提供的 .index()
方法來取得某個元素在這個串列裡的索引值:
>>> heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
>>> heroes.index("悟空")
1
"悟空"
在第 2 個位置,所以會得到索引值 1。不過要注意如果試著問它某個不存在的元素的話,像這樣:
>>> heroes.index("芙莉蓮")
如果 "芙莉蓮"
不在 heroes
串列裡,在其他程式語言可能不會出錯而是得到 -1
或是 undefined
之類的答案,表示不在隊伍裡。但 Python 會給你一個 ValueError
的錯誤訊息,很明確的告訴你這裡沒有這個人:
ValueError: '芙莉蓮' is not in list
另外還有一個方法是使用串列的 .count()
方法,這個方法會回傳某個元素在串列裡出現的次數,如果完全沒有出現的話會得到 0:
>>> heroes = ["五條悟", "鳴人", "鳴人", "鳴人", "鳴人", "鳴人", "三井壽"]
>>> heroes.count("鳴人")
5
>>> heroes.count("魯夫")
0
在上面這幾個方法,雖然我們使用串列的 .index()
或 .count()
方法來得知某個元素是否存在串列裡,但如果只是要判斷有或沒有的話,使用 in
關鍵字得到布林值會更適合而且更不容易出錯,看看底下這個例子,想想看答案是什麼:
heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
if heroes.index("五條悟"):
print("找到了")
else:
print("沒找到")
乍看之下,你可能會以為會印出 找到了
,但答案卻剛好相反。這是因為 .index()
方法會得到索引值 0,在前面布林值章節曾經介紹過數字 0 在 Python 裡會被判定成 False
,所以反 而會印出 沒找到
,這結果可能不是你想要的。
印出所有元素
如果想把串列裡的所有元素都印出來,可以使用上個章節學到的 for
迴圈:
heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
for hero in heroes:
print(hero)
這裡我會使用 hero
當做迴圈裡的變數名稱,而不是隨便用個 a
或 i
這種沒什麼相對比較沒有意義的名字。變數叫什麼名字對 Python 來說並沒有差別,也沒有強制規定,反正執行之後的結果都一樣。但我希望在迴圈裡的變數名稱可以看起來知道自己在做什麼,所以我遇到這種一整群的資料通常會用複數型態方式來命名,個別的變數則會使用單數型態,這樣程式碼的可讀性會好一些。如果用 while
迴圈也沒問題,只是得額外加個變數:
heroes = ["五條悟", "悟空", "鳴人", "三井壽"]
i = 0
while i < len(heroes):
print(heroes[i])
i += 1
看起來還是 for
迴圈比較簡單一點。
新增元素
串列有一些不錯用的方法,可以讓原有的串列加入新的元素,例如 .append()
方法可以把新的元素加到串列的最後面:
>>> heroes = ["悟空", "鳴人", "芙莉蓮"]
>>> heroes.append("娜美")
>>> heroes
['悟空', '鳴人', '芙莉蓮', '娜美']
這樣就可以把 "娜美"
加到 heroes
串列的最後面了。.append()
方法一次只能新增一個元素,如果想一口氣加入多個元素,可以使用 .extend()
方法來幫串列進行「擴充」:
>>> heroes = ["悟空", "鳴人", "芙莉蓮"]
>>> heroes.extend(["娜美", "魯夫", "索隆"]) # 一次加入 3 個
>>> heroes
['悟空', '鳴人', '芙莉蓮', '娜美', '魯夫', '索隆']
如果是要把元素從串列前面加進來的話,則是使用 .insert()
方法搭配索引值就能新的元素安插到指定的位置上,例如:
>>> heroes = ["悟空", "鳴人", "芙莉蓮"]
>>> heroes.insert(1, "魯夫")
>>> heroes
['悟空', '魯夫', '鳴人', '芙莉蓮']
.insert(1, "魯夫")
的意思是指要在索引值 1,也就是這個串列第 2 格的位置,安插元素 "魯夫"
。如果有看懂 .insert()
的用法的話,把索引值設定成 0 就等同於在最前面加入新元素,而把索引值設定成 len(heroes)
就等於是在最後面加入新元素,例如:
>>> heroes = ["悟空", "鳴人", "芙莉蓮"]
>>> heroes.insert(0, "五條悟")
>>> heroes
['五條悟', '悟空', '鳴人', '芙莉蓮']
移除元素
如果你對某個元素不滿意,想把它刪掉有好幾種做法,首先可使用串列的 .pop()
方法並搭配索引值就能把指定的元素拿出來:
>>> heroes = ["五條悟", "悟空", "鳴人", "芙莉蓮", "三井壽"]
# 拿掉最後一個元素
>>> heroes.pop()
'三井壽'
>>> heroes
['五條悟', '悟空', '鳴人', '芙莉蓮']
# 拿掉第一個元素
>>> heroes.pop(0)
'五條悟'
>>> heroes
['悟空', '鳴人', '芙莉蓮']
# 拿掉第二個元素
>>> heroes.pop(1)
'鳴人'
>>> heroes
['悟空', '芙莉蓮']
.pop()
方法如果沒有給索引值的話,會把串最的最後一個元素拿出來,如果有給索引值的話,則會取出索引值所指的元素。但萬一串列裡已經沒東西的話:
heroes = []
heroes.pop() # 這裡會...?
已經沒有東西可以拿啦!這時候 Python 會給你一個 IndexError
錯誤訊息,並且告訴你這個串列已經是空的了:
IndexError: pop from empty list
要刪除串列裡的元素,另一個做法是使用 .remove()
方法:
>>> heroes = ["五條悟", "悟空", "悟空", "鳴人", "三井壽"]
>>> heroes.remove("鳴人")
>>> heroes
['五條悟', '悟空', '悟空', '三井壽']
.remove()
方法可以帶入某個元素,如果在這個串列裡有出現的話就會把它移掉。不過需要注意的是,如果這個串列裡有兩個同樣的元素的時候,.remove()
方法只會拿掉第一個,剩下的會留著;
>>> heroes = ["五條悟", "悟空", "悟空", "鳴人", "三井壽"]
>>> heroes.remove("悟空")
>>> heroes
['五條悟', '悟空', '鳴人', '三井壽']
如果想要把所有的 "悟空"
都移掉的話,可以用我們在上個章節學到的迴圈:
heroes = ["五條悟", "悟空", "悟空", "鳴人", "三井壽"]
while "悟空" in heroes:
heroes.remove("悟空")
print(heroes) # 印出 ['五條悟', '鳴人', '三井壽']