1. 程式人生 > >Python3簡明教程(六)—— 數據結構

Python3簡明教程(六)—— 數據結構

遇到 none 副作用 reverse 現在 ont 都是 usr 有用

簡單的來說,數據結構(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 doesnt 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簡明教程(六)—— 數據結構