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" 錯誤。

   引數 *start* 和 *end* 的定義跟在 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'

你可能會注意到一些方法，像是 "insert" 、 "remove" 或者是 "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 在這種使用方式下
效率較差。使用 "append" 和 "pop" 來加入和取出尾端的元素較快，而使用
"insert" 和 "pop" 來插入和取出頭端的元素較慢（因為其他元素都需要挪動一
格）。

如果要實作 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)]

這樣更簡潔和易讀。

A list comprehension consists of brackets containing an expression
followed by a "for" clause, then zero or more "for" or "if" clauses.
The result will be a new list resulting from evaluating the expression
in the context of the "for" and "if" clauses which follow it. For
example, this listcomp combines the elements of two lists if they are
not equal:

   >>> [(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" 在這兩段程式裡的順序是相同的。

如果 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. The "del" statement
========================

There is a way to remove an item from a list given its index instead
of its value: the "del" statement.  This differs from the "pop()"
method which returns a value.  The "del" statement can also be used to
remove slices from a list or clear the entire list (which we did
earlier by assignment of an empty list to the slice).  For example:

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

"del" 也可以用來刪除變數：

   >>> del a

刪除之後，對 "a" 的參照將會造成錯誤（至少在另一個值又被指派到它之前）
。我們將在後面看到更多關於 "del" 的其他用法。


5.3. Tuples 和序列 (Sequences)
==============================

我們看到 lists 和 strings 有許多共同的特性，像是索引操作 (indexing) 以
及切片操作 (slicing) 。他們是 *序列* 資料結構中的兩個例子（請參考
Sequence Types --- list, tuple, range ）。由於 Python 是個持續發展中的
語言，未來可能還會有其他的序列資料結構加入。接著要介紹是下一個標準序列
資料結構： *tuple* 。

一個 tuple 是由若干個值藉由逗號區隔而組成，例如：

   >>> t = 12345, 54321, 'hello!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'hello!')
   >>> # Tuples may be nested:
   ... u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
   >>> # Tuples are immutable:
   ... t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # but they can contain mutable objects:
   ... v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])

如同我們看到的，被輸出的 tuple 總是以括號包著，如此巢狀的 tuple 才能被
正確的直譯 (interpret)；他們可以是以被括號包著或不被包著的方式當作輸入
，雖然括號的使用常常是有其必要的（譬如此 tuple 是一個較大的陳述式的一
部分）。指派東西給 tuple 中個別的項是不行的，但是可以在 tuple 中放入含
有可變項的物件，譬如 list 。

雖然 tuple 和 list 看起來很類似，但是他們通常用在不同的情況與不同目的
。 tuple 是 *immutable* （不可變的），通常儲存異質的序列元素，並可經由
拆解(unpacking) （請參考本節後段）或索引 (indexing) 來存取（或者在使用
"namedtuples" 的時候藉由屬性 (attribute) 來存取）。 list 是 *mutable*
（可變的），其元素通常是同質的且可藉由迭代整個串列來存取。

一個特別的議題是，關於創建一個含有 0 個或 1 個項目的 tuple：語法上會採
納一些奇怪的用法。空的 tuple 藉由一對空括號來創建；含有一個項目的
tuple 經由一個值加上一個逗點來創建（不需要用括號把一個單一的值包住）。
醜，但有效率。例如：

   >>> empty = ()
   >>> singleton = 'hello',    # <-- note trailing comma
   >>> len(empty)
   0
   >>> len(singleton)
   1
   >>> singleton
   ('hello',)

陳述式 "t = 12345, 54321, 'hello!'" 就是一個 *tuple packing* 的例子：
"12345" ， "54321" 和 "'hello!'" 一起被放進 tuple 裡。反向操作也可以：

   >>> x, y, z = t

這個正是我們所說序列拆解 (*sequence unpacking*) ，可運用在任何位在等號
右邊的序列。序列拆解要求等號左邊的變數數量必須與等號右邊的序列中的元素
數量相同。注意，多重指派就只是 tuple packing 和序列拆解的結合而已。


5.4. 集合 (Sets)
================

Python 也包含了一種用在集合 (*sets*) 的資料結構。一個 set 是一組無序且
沒有重複的元素。基本的使用方式包括了成員測試和消除重複項。 Set 物件也
支援聯集，交集，差集和互斥等數學操作。

大括號或 "set()" 函式都可以用來創建 set 。注意：要創建一個空的 set ，
我們必須使用 "set()" 而不是 "{}" ；後者會創建一個空的 dictionary ，一
種我們將在下一節討論的資料結構。

這裡是一個簡單的演示：

   >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
   >>> print(basket)                      # show that duplicates have been removed
   {'orange', 'banana', 'pear', 'apple'}
   >>> 'orange' in basket                 # fast membership testing
   True
   >>> 'crabgrass' in basket
   False

   >>> # Demonstrate set operations on unique letters from two words
   ...
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # unique letters in a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # letters in a but not in b
   {'r', 'd', 'b'}
   >>> a | b                              # letters in a or b or both
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # letters in both a and b
   {'a', 'c'}
   >>> a ^ b                              # letters in a or b but not both
   {'r', 'd', 'b', 'm', 'z', 'l'}

和 list comprehensions 類似，也有 set comprehensions （集合綜合運算）
：

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}


5.5. 字典（Dictionary）
=======================

下一個常用的 python 內建資料結構為 *dictionary* （請參考 Mapping Types
--- dict ）。 Dictionary 有時被稱為 "關聯記憶體" (associative
memories) 或 "關聯矩陣" (associative arrays) 。不像序列是由一個範圍內
的數字當作索引， dictionary 是由 *key* （鍵）來當索引， key 可以是任何
不可變的型態；字串和數字都可以當作 key 。 Tuple 也可以當作 key 如果他
們只含有字串、數字或 tuple；若一個 tuple 直接或間接地含有任何可變的物
件，它就不能當作 key 。我們無法使用 list 當作 key ，因為 list 可以經由
索引操作、切片操作或是方法像是 "append()" 和 "extend()" 來修改。

思考 dict 最好的方式是把它想成是一組鍵值對 (*key: value* pair) 的集合
，其中 key 在同一個 dictionary（字典）裡必須是獨一無二的。使用一對大括
號可創建一個空的字典 ："{}"。將一串由逗號分隔的鍵值對置於大括號則可初
始化字典。這同樣也是字典輸出時的格式。

Dict 主要的操作為藉由鍵來儲存一個值並且可藉由該鍵來取出該值。也可以使
用 "del" 來刪除鍵值對。如果我們使用用過的鍵來儲存，該鍵所對應的較舊的
值會被覆蓋。使用不存在的鍵來取出值會造成錯誤。

對字典使用 "list(d)" 會得到一個包含該字典所有鍵（key）的 list，其排列
順序為插入時的順序。（若想要排序，則使用 "sorted(d)" 代替即可）。如果
想確認一個鍵是否已存在於字典中，可使用關鍵字 "in" 。

這是個使用一個字典的簡單範例：

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

函式 "dict()" 可直接透過一串鍵值對序列來創建 dict：

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

此外， dict comprehensions 也可以透過鍵與值的陳述式來創建 dict ：

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

當鍵是簡單的字串時，使用關鍵字引數 (keyword arguments) 有時會較為簡潔
：

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. 迴圈技巧
=============

當對 dict 作迴圈時，鍵以及其對應的值可以藉由使用 "items()" 方法來同時
取得：

   >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
   >>> for k, v in knights.items():
   ...     print(k, v)
   ...
   gallahad the pure
   robin the brave

當對序列作迴圈時，位置索引及其對應的值可以藉由使用 "enumerate()" 函式
來同時取得：

   >>> for i, v in enumerate(['tic', 'tac', 'toe']):
   ...     print(i, v)
   ...
   0 tic
   1 tac
   2 toe

要同時對兩個以上的序列作迴圈，可以將其以成對的方式放入 "zip()" 函式：

   >>> questions = ['name', 'quest', 'favorite color']
   >>> answers = ['lancelot', 'the holy grail', 'blue']
   >>> for q, a in zip(questions, answers):
   ...     print('What is your {0}?  It is {1}.'.format(q, a))
   ...
   What is your name?  It is lancelot.
   What is your quest?  It is the holy grail.
   What is your favorite color?  It is blue.

要對序列作反向的迴圈，首先先寫出正向的序列，在對其使用 "reversed()" 函
式：

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

要以迴圈對序列作排序，使用 "sorted()" 函式會得到一個新的經排序過的
list ，但不會改變原本的序列：

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for f in sorted(set(basket)):
   ...     print(f)
   ...
   apple
   banana
   orange
   pear

有時我們會想要以迴圈來改變的一個 list，但是，通常創建一個新的 list 會
更簡單且安全：

   >>> import math
   >>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> filtered_data = []
   >>> for value in raw_data:
   ...     if not math.isnan(value):
   ...         filtered_data.append(value)
   ...
   >>> filtered_data
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. 更多條件式主題
===================

使用在 "while" 和 "if" 內的陳述式可以包含任何運算子，而不是只有比較運
算子 (comparisons) 。

比較運算子 "in" 和 "not in" 檢查一個數值是否存在（不存在）於一個序列中
。運算子 "is" 和 "not is" 比較兩個物件是否真的是相同的物件；這對可變物
件例如 list 來說很重要。所有的比較運算子優先度都相同且都低於數值運算子
。

比較運算是可以串連在一起的。例如， "a < b == c" 就是在測試 "a" 是否小
於 "b" 和 "b" 是否等於 "c" 。

比較運算可以結合布林運算子 "and" 和 "or" ，且一個比較運算的結果（或任
何其他布林表達式）可以加上 "not" 來否定。這些運算子的優先度都比比較運
算子還低，其中， "not" 的優先度最高， "or" 的優先度最低，因此 "A and
not B or C" 等同於 "(A and (not B)) or C" 。一如往常，括號可以用來表示
任何想要的組合。

布林運算子 "and" 和 "or" 也被稱為短路 (*short-circuit*) 運算子：會將其
引數從左至右進行運算，當結果出現時即結束運算。例如，若 "A" 和 "C" 為真
但 "B" 為假，則 "A and B and C" 的運算並不會執行到 "C" 。當運算結果被
當成一般數值而非布林值時，短路運算子的回傳值為最後被運算的引數。

將一個比較運算或其他布林表達式的結果指派給一個變數是可以的。例如：

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   'Trondheim'

注意， Python 不像 C 語言，在表達式裡進行指派是不行的。 C 語言的程式設
計師可能會抱怨這件事，但這樣做避免了在 C 語言裡常見的一種問題：想要打
"==" 卻在表達式裡輸入 "=" 。


5.8. 序列和其他資料結構之比較
=============================

序列物件可以拿來和其他相同型態的物件做比較。這種比較使用詞典式順序
(*lexicographical* ordering) ：首先比較各自最前面的那項，若不相同，便
可決定結果，若相同，則比較下ㄧ項，以此類推，直到其中一個序列完全用完。
如果被拿出來比較的兩項本身又是相同的序列型態，則詞典式順序的比較會遞迴
處理。如果兩個序列所有的項都相等，則此兩個序列被認為是相等的。如果其中
一個序列是另一個的子序列，則較短的那個序列為較小的序列。字串的詞典式順
序使用 Unicode 的碼位 (code point) 編號來排序個別字元。以下是一些相同
序列型態的比較：

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1.0, 2.0, 3.0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

注意，若使用 "<" 或 ">" 來比較不同型態的物件是合法的，表示物件擁有適當
的比較方法。例如，混合型數值比較是根據它們數字的值來做比較，所以 0 等
於 0.0，等等。否則直譯器會選擇丟出一個 "TypeError" 錯誤而不是提供一個
任意的排序。

-[ 註解 ]-

[1] 其他語言可能可以回傳可變的物件並允許方法串連，例如
    "d->insert("a")->remove("b")->sort();" 。
