Python3簡明教程(六)—— 數據結構
簡單的來說,數據結構(data structure)是計算機中存儲、組織數據的方式。比如我們之前使用過的列表就是一種數據結構,在這裏我們還會深入學習它。之前也有簡單的介紹。
列表
>>> a = [23, 45, 1, -3434, 43624356, 234] >>> a.append(45) >>> a [23, 45, 1, -3434, 43624356, 234, 45]
首先我們建立了一個列表 a
。然後調用列表的方法 a.append(45)
添加元素 45
到列表末尾。你可以看到元素 45 已經添加到列表的末端了。有些時候我們需要將數據插入到列表的任何位置,這時我們可以使用列表的 insert()
>>> a.insert(0, 1) # 在列表索引 0 位置添加元素 1 >>> a [1, 23, 45, 1, -3434, 43624356, 234, 45] >>> a.insert(0, 111) # 在列表索引 0 位置添加元素 111 >>> a [111, 1, 23, 45, 1, -3434, 43624356, 234, 45]
具體來說是,插入位置後元素後移,這樣保證了插入後新添加元素就是索引的位置。
列表方法 count(s)
會返回列表元素中 s
的數量。我們來檢查一下 45
>>> a.count(45)
2
如果你想要在列表中移除任意指定值,你需要使用 remove()
方法。
>>> a.remove(234) >>> a [111, 1, 23, 45, 1, -3434, 43624356, 45]
現在我們反轉整個列表。
>>> a.reverse() >>> a [45, 43624356, -3434, 1, 45, 23, 1, 111]
怎樣將一個列表的所有元素添加到另一個列表的末尾呢,可以使用列表的 extend()
>>> b = [45, 56, 90] >>> a.extend(b) # 添加 b 的元素而不是 b 本身 >>> a [45, 43624356, -3434, 1, 45, 23, 1, 111, 45, 56, 90]
給列表排序,我們使用列表的 sort()
方法,排序的前提是列表的元素是可比較的。
>>> a.sort() >>> a [-3434, 1, 1, 23, 45, 45, 45, 56, 90, 111, 43624356]
你也能使用 del
關鍵字刪除指定位置的列表元素。
>>> del a[-1] >>> a [-3434, 1, 1, 23, 45, 45, 45, 56, 90, 111]
將列表用作棧和隊列
棧是我們通常所說的一種 LIFO (Last In First Out 後進先出)數據結構。它的意思是最後進入的數據第一個出來。一個最簡單的例子往一端封閉的管道放入一些彈珠然後取出來,如果你想把彈珠取出來,你必須從你最後放入彈珠的位置挨個拿出來。用代碼實現此原理:
>>> a = [1, 2, 3, 4, 5, 6] >>> a [1, 2, 3, 4, 5, 6] >>> a.pop() 6 >>> a.pop() 5 >>> a.pop() 4 >>> a.pop() 3 >>> a [1, 2] >>> a.append(34) >>> a
上面的代碼中我們使用了一個新方法 pop()
。傳入一個參數 i 即 pop(i)
會將第 i 個元素彈出。請註意,沒有push()函數,因為append()函數就能實現了。
在我們的日常生活中會經常遇到隊列,比如售票窗口、圖書館、超市的結賬出口。隊列是一種在末尾追加數據以及在開始彈出數據的數據結構。與棧不同,它是 FIFO (First In First Out 先進先出)的數據結構。
>>> a = [1, 2, 3, 4, 5] >>> a.append(1) >>> a [1, 2, 3, 4, 5, 1] >>> a.pop(0) 1 >>> a.pop(0) 2 >>> a [3, 4, 5, 1]
我們使用 a.pop(0)
彈出列表中第一個元素。可見棧和隊列在代碼實現上沒有多大區別,只是出隊一個從隊首,一個從隊尾。
列表推導式
列表推導式為從序列中創建列表提供了一個簡單的方法。
如果沒有列表推導式,一般都是這樣創建列表的:通過將一些操作應用於序列的每個成員並通過返回的元素創建列表,或者通過滿足特定條件的元素創建子序列。
>>> squares = [] >>> for x in range(10): ... squares.append(x**2) ... >>> squares [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
註意這個 for 循環中的被創建(或被重寫)的名為 x
的變量在循環完畢後依然存在。
使用如下方法,我們可以計算 squares 的值而不會產生任何的副作用:
squares = list(map(lambda x: x**2, range(10)))
等價於下面的列表推導式。
squares = [x**2 for x in range(10)]
上面這個方法更加簡明且易讀。
列表推導式由包含一個表達式的中括號組成,表達式後面跟隨一個 for 子句,之後可以有零或多個 for 或 if 子句。結果是一個列表,由表達式依據其後面的 for 和 if 子句上下文計算而來的結果構成。
例如,如下的列表推導式結合兩個列表的元素,且元素之間不相等:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y] [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
等同於:
>>> combs = [] >>> for x in [1,2,3]: ... for y in [3,1,4]: ... if x != y: ... combs.append((x, y)) ... >>> combs [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
值得註意的是在上面兩個方法中的 for 和 if 語句的順序。
列表推導式也可以嵌套。
>>> a=[1,2,3] >>> z = [x + 1 for x in [x ** 2 for x in a]] >>> z [2, 5, 10]
元組
元組是由數個逗號分割的值組成,一般用小括號括起來,也可以省略。
>>> a = ‘Fedora‘, ‘ShiYanLou‘, ‘Kubuntu‘, ‘Pardus‘ >>> a (‘Fedora‘, ‘ShiYanLou‘, ‘Kubuntu‘, ‘Pardus‘) >>> a[1] ‘ShiYanLou‘ >>> for x in a: ... print(x, end=‘ ‘) ... Fedora ShiYanLou Kubuntu Pardus
你可以對任何一個元組執行拆封操作並賦值給多個變量,就像下面這樣:
>>> divmod(15,2) (7, 1) >>> x, y = divmod(15,2) >>> x 7 >>> y 1
元組是不可變類型,這意味著你不能在元組內刪除或添加或編輯任何值。如果你嘗試這些操作,將會出錯:
>>> a = (1, 2, 3, 4) >>> del a[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ‘tuple‘ object doesn‘t support item deletion >>> a.append(2) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: ‘tuple‘ object has no attribute ‘append‘
要創建只含有一個元素的元組,在值後面跟一個逗號。
>>> a = (123) >>> a 123 >>> type(a) <class ‘int‘> >>> a = (123, ) >>> b = 321, >>> a (123,) >>> b (321,) >>> type(a) <class ‘tuple‘> >>> type(b) <class ‘tuple‘>
通過內建函數 type()
你可以知道任意變量的數據類型。還記得我們使用 len()
函數來查詢任意序列類型數據的長度嗎?
>>> type(len) <class ‘builtin_function_or_method‘> >>> type(print) <class ‘builtin_function_or_method‘>
集合
集合是一個無序不重復元素的集。基本功能包括關系測試和消除重復元素。集合對象還支持 union(聯合),intersection(交),difference(差)和 symmetric difference(對稱差集)等數學運算。
大括號或 set() 函數可以用來創建集合。註意:想要創建空集合,你必須使用 set() 而不是 {}。後者用於創建空字典,我們在下一節中介紹的一種數據結構。
下面是集合的常見操作:
>>> basket = {‘apple‘, ‘orange‘, ‘apple‘, ‘pear‘, ‘orange‘, ‘banana‘} >>> print(basket) # 你可以看到重復的元素被去除 {‘orange‘, ‘banana‘, ‘pear‘, ‘apple‘} >>> ‘orange‘ in basket True >>> ‘crabgrass‘ in basket False >>> # 演示對兩個單詞中的字母進行集合操作 ... >>> a = set(‘abracadabra‘) >>> b = set(‘alacazam‘) >>> a # a 去重後的字母 {‘a‘, ‘r‘, ‘b‘, ‘c‘, ‘d‘} >>> a - b # a 有而 b 沒有的字母 {‘r‘, ‘d‘, ‘b‘} >>> a | b # 存在於 a 或 b 的字母 {‘a‘, ‘c‘, ‘r‘, ‘d‘, ‘b‘, ‘m‘, ‘z‘, ‘l‘} >>> a & b # a 和 b 都有的字母 {‘a‘, ‘c‘} >>> a ^ b # 存在於 a 或 b 但不同時存在的字母 {‘r‘, ‘d‘, ‘b‘, ‘m‘, ‘z‘, ‘l‘}
從集合中添加或彈出元素:
>>> a = {‘a‘,‘e‘,‘h‘,‘g‘} >>> a.pop() ‘h‘ >>> a.add(‘c‘) >>> a {‘c‘, ‘e‘, ‘g‘, ‘a‘}
字典
字典是是無序的鍵值對(key:value
)集合,同一個字典內的鍵必須是互不相同的。一對大括號 {} 創建一個空字典。初始化字典時,在大括號內放置一組逗號分隔的鍵:值對,這也是字典輸出的方式。我們使用鍵來檢索存儲在字典中的數據。
>>> data = {‘kushal‘:‘Fedora‘, ‘kart_‘:‘Debian‘, ‘Jace‘:‘Mac‘} >>> data {‘kushal‘: ‘Fedora‘, ‘Jace‘: ‘Mac‘, ‘kart_‘: ‘Debian‘} >>> data[‘kart_‘] ‘Debian‘
創建新的鍵值對很簡單:
>>> data[‘parthan‘] = ‘Ubuntu‘ >>> data {‘kushal‘: ‘Fedora‘, ‘Jace‘: ‘Mac‘, ‘kart_‘: ‘Debian‘, ‘parthan‘: ‘Ubuntu‘}
使用 del
關鍵字刪除任意指定的鍵值對:
>>> del data[‘kushal‘] >>> data {‘Jace‘: ‘Mac‘, ‘kart_‘: ‘Debian‘, ‘parthan‘: ‘Ubuntu‘
使用 in
關鍵字查詢指定的鍵是否存在於字典中:
>>> ‘ShiYanLou‘ in data False
dict() 可以從包含鍵值對的元組中創建字典:
>>> dict(((‘Indian‘,‘Delhi‘),(‘Bangladesh‘,‘Dhaka‘))) {‘Indian‘: ‘Delhi‘, ‘Bangladesh‘: ‘Dhaka‘}
如果你想要遍歷一個字典,使用字典的 items()
方法:
>>> data {‘Kushal‘: ‘Fedora‘, ‘Jace‘: ‘Mac‘, ‘kart_‘: ‘Debian‘, ‘parthan‘: ‘Ubuntu‘} >>> for x, y in data.items(): ... print("{} uses {}".format(x, y)) ... Kushal uses Fedora Jace uses Mac kart_ uses Debian parthan uses Ubuntu
許多時候我們需要往字典中的元素添加數據,我們首先要判斷這個元素是否存在,不存在則創建一個默認值。如果在循環裏執行這個操作,每次叠代都需要判斷一次,降低程序性能。
我們可以使用 dict.setdefault(key, default)
更有效率的完成這個事情:
>>> data = {} >>> data.setdefault(‘names‘, []).append(‘Ruby‘) >>> data {‘names‘: [‘Ruby‘]} >>> data.setdefault(‘names‘, []).append(‘Python‘) >>> data {‘names‘: [‘Ruby‘, ‘Python‘]} >>> data.setdefault(‘names‘, []).append(‘C‘) >>> data {‘names‘: [‘Ruby‘, ‘Python‘, ‘C‘]}
試圖索引一個不存在的鍵將會拋出一個 keyError 錯誤。我們可以使用 dict.get(key, default)
來索引鍵,如果鍵不存在,那麽返回指定的 default 值:
>>> data[‘foo‘] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: ‘foo‘ >>> data.get(‘foo‘, 0) 0
如果你想要在遍歷列表(或任何序列類型)的同時獲得元素索引值,你可以使用 enumerate()
。
>>> for i, j in enumerate([‘a‘, ‘b‘, ‘c‘]): ... print(i, j) ... 0 a 1 b 2 c
你也許需要同時遍歷兩個序列類型,你可以使用 zip()
函數。請註意,與寫成二重循環是不同的。
>>> a = [‘Pradeepto‘, ‘Kushal‘] >>> b = [‘OpenSUSE‘, ‘Fedora‘] >>> for x, y in zip(a, b): ... print("{} uses {}".format(x, y)) ... Pradeepto uses OpenSUSE Kushal uses Fedora
綜合示例
這個例子裏我們計算兩個矩陣的 Hadamard 乘積。要求輸入矩陣的行/列數(在這裏假設我們使用的是 n × n 的矩陣)。
#!/usr/bin/env python3 n = int(input("Enter the value of n: ")) print("Enter values for the Matrix A") a = [] for i in range(n): a.append([int(x) for x in input().split()]) print("Enter values for the Matrix B") b = [] for i in range(n): b.append([int(x) for x in input().split()]) c = [] for i in range(n): c.append([a[i][j] * b[i][j] for j in range(n)]) print("After matrix multiplication") print("-" * 7 * n) for x in c: for y in x: print(str(y).rjust(5), end=‘ ‘) print() print("-" * 7 * n)
這裏我們使用了幾次列表推導式。[int(x) for x in input().split()]
首先通過 input()
獲得用戶輸入的字符串,再使用 split()
分割字符串得到一系列的數字字符串,然後用 int()
從每個數字字符串創建對應的整數值。我們也使用了 [a[i][j] * b[i][j] for j in range(n)]
來得到矩陣乘積的每一行數據。
一般來說,目前我們見到的數據結構已經夠用了,不過 Python 中還有一些其它有用的數據結構,可以在這裏了解:https://docs.python.org/3/library/datatypes.html。
參考鏈接:https://www.shiyanlou.com/courses/596
Python3簡明教程(六)—— 數據結構