5. 資料結構

這個章節將會更深入的介紹一些你已經學過的東西的細節上,並且加入一些你還沒有接觸過的部分。

5.1. 進一步了解 List(串列)

List(串列)這個資料型態,具有更多操作的方法。下面條列了所有關於 list 的物件方法:

list.append(x)

將一個新的項目加到 list 的尾端。等同於 a[len(a):] = [x]

list.extend(iterable)

將 iterable(可列舉物件)接到 list 的尾端。等同於 a[len(a):] = iterable

list.insert(i, x)

將一個項目插入至 list 中給定的位置。第一個引數為插入處前元素的索引值,所以 a.insert(0, x) 會插入為 list 首位,而 a.insert(len(a), x) 則相當於 a.append(x)

list.remove(x)

Remove the first item from the list whose value is equal to x. It raises a ValueError if there is no such item.

list.pop([i])

移除 list 中給定位置的項目,並回傳它。如果沒有指定位置, a.pop() 將會移除 list 中最後的項目並回傳它。(在 i 周圍的方括號代表這個參數是選用的,並不代表你應該在該位置輸入方括號。你將會常常在 Python 函式庫參考指南中看見這個表示法)

list.clear()

刪除 list 中所有項目。這等同於 del a[:]

list.index(x[, start[, end]])

回傳 list 中第一個值等於 x 的項目之索引值(從零開始的索引)。若 list 中無此項目,則丟出 ValueError 錯誤。

引數 startend 的定義跟在 slice 表示法中相同,搜尋的動作被這兩個引數限定在 list 中特定的子序列。但要注意的是,回傳的索引值是從 list 的開頭開始算,而不是從 start 開始算。

list.count(x)

回傳數值為 x 在 list 中所出現的次數。

list.sort(key=None, reverse=False)

將 list 中的項目排序。(有參數可以使用來進行客製化的排序,請參考 sorted() 部分的解釋)

list.reverse()

將 list 中的項目前後順序反過來。

list.copy()

回傳一個淺複製 (shallow copy) 的 list 。等同於 a[:]

以下是一個使用到許多 list 物件方法的例子:

>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4)  # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'

你可能會注意到一些方法,像是 insertremove 或者是 sort ,並不會印出回傳的值,事實上,他們回傳預設值 None [1]。這是一個用於 Python 中所有可變資料結構的設計法則。

5.1.1. 將 List 作為 Stack(堆疊)使用

List 的操作方法使得它非常簡單可以用來實作 stack(堆疊)。Stack 為一個遵守最後加入元素最先被取回(後進先出,」last-in, first-out」)規則的資料結構。你可以使用方法 append() 將一個項目放到堆疊的頂層。而使用方法 pop() 且不給定索引值去取得堆疊最上面的項目。舉例而言:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]

5.1.2. 將 List 作為 Queue(佇列)使用

我們也可以將 list 當作 queue(佇列)使用,即最先加入元素最先被取回(先進先出,」first-in, first-out」)的資料結構。然而,list 在這種使用方式下效率較差。使用 appendpop 來加入和取出尾端的元素較快,而使用 insertpop 來插入和取出頭端的元素較慢(因為其他元素都需要挪動一格)。

如果要實作 queue,請使用 collections.deque ,其被設計成能快速的從頭尾兩端加入和取出。例如:

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.1.3. List Comprehensions(串列綜合運算)

List Comprehension(串列綜合運算)讓你可以用簡潔的方法創建 list。常見的應用是基於一個 list 或 iterable(可列舉物件),將每一個元素經過某個運算的結果串接起來成為一個新的 list 。或是創建一個 list 的子序列,其每一個元素皆滿足一個特定的條件。

舉例來說,假設我們要創建一個「平方的 list」:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

注意這是創建(或複寫)一個變數叫 x 其在迴圈結束後仍然存在。我們可以這樣產生平方串列而不造成任何 side effects(副作用):

squares = list(map(lambda x: x**2, range(10)))

或與此相等的:

squares = [x**2 for x in range(10)]

這樣更簡潔和易讀。

一個 list comprehension 的組成,是包含著一個 expression(運算式)和一個 for 語句,再接著零個或多個 forif 語句的一對方括號。結果會是一個新的串列,內容是在接著的 forif 語句的環境下,執行前面 expression 的結果。例如,這個 list comprehension 是由兩個串列中互相不同的元素組合所組成:

>>> [(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)]

注意 forif 在這兩段程式裡的順序是相同的。

如果 expression 是一個 tuple(例如上面例子中的 (x, y)),它必須加上小括弧:

>>> vec = [-4, -2, 0, 2, 4]
>>> # create a new list with the values doubled
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # filter the list to exclude negative numbers
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # apply a function to all the elements
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # call a method on each element
>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # create a list of 2-tuples like (number, square)
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # the tuple must be parenthesized, otherwise an error is raised
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in <module>
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

List comprehensions 可以含有複雜的 expression 和巢狀的函式呼叫:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4. 巢狀的 List Comprehensions

最初放在 list comprehesion 中的 expression 可以是任何形式的 expression,包括再寫一個 list comprehension。

考慮以下表示 3x4 矩陣的範例,使用 list 包含 3 個長度為 4 的 list :

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

以下的 list comprehesion 會將矩陣的行與列作轉置:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

如同我們在上一節看到的,此巢狀的 list comprehension 為一個 list comprehension在 for 之前先被計算,接著再作一次 list comprehension,所以,這個例子和下者相同:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

因此,也和下者相同:

>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

在實際運用上,我們傾向於使用內建函式 (built-in functions) 而不是複雜的流程控制陳述式。在這個例子中,使用 zip() 函式會非常有效率:

>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

關於星號的更多細節,請參考 Unpacking Argument Lists

5.2. del 陳述式

有一個方法可以藉由索引而不是值來刪除 list 中的項: del 陳述式。這和 pop() 方法傳回一個值不同, del 陳述式可以用來刪除 list 中的片段或者清空整個 list(我們之前藉由指派一個空的 list 給想刪除的片段來完成這件事)。例如:

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>>