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)

   从列表中删除第一个值为 *x* 的元素。未找到指定元素时，触发
   "ValueError" 异常。

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
中所有可變資料結構的設計法則。

还有，不是所有数据都可以排序或比较。例如，"[None, 'hello', 10]" 就不可
排序，因为整数不能与字符串对比，而 *None* 不能与其他类型对比。有些类型
根本就没有定义顺序关系，例如，"3+4j < 5+7j" 这种对比操作就是无效的。


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

這樣更簡潔和易讀。

列表推导式的方括号内包含以下内容：一个表达式，后面为一个 "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" 在這兩段程式裡的順序是相同的。

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

關於星號的更多細節，請參考 解包实参列表 。


5.2. "del" 语句
===============

"del" 语句按索引，而不是值从列表中移除元素。与返回值的 "pop()" 方法不
同， "del" 语句也可以从列表中移除切片，或清空整个列表（之前是将空列表
赋值给切片）。 例如：

   >>> 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) 。他們是 *序列* 資料結構中的兩個例子（請參考 序列
类型 --- 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* （請參考 映射类型 ---
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 程序中常见的问题：要在表达式中写 "==" 时，却写成了 "="。


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

序列对象可以与相同序列类型的其他对象比较。这种比较使用 *字典式* 顺序：
首先，比较前两个对应元素，如果不相等，则可确定比较结果；如果相等，则比
较之后的两个元素，以此类推，直到其中一个序列结束。如果要比较的两个元素
本身是相同类型的序列，则递归地执行字典式顺序比较。如果两个序列中所有的
对应元素都相等，则两个序列相等。如果一个序列是另一个的初始子序列，则较
短的序列可被视为较小（较少）的序列。 对于字符串来说，字典式顺序使用
Unicode 码位序号排序单个字符。下面列出了一些比较相同类型序列的例子：

   (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();" 。
