#!/usr/bin/env python # coding: utf-8 #

# # Open In Jupyter nbviewer # # #

# [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/twMr7/Python-Machine-Learning/blob/master/05-List_Operations.ipynb) # # # 5. `list` 序列容器操作 # # `list` 是存放序列性資料的結構。語法使用逗號 `,` 分隔資料元素,用中括號(square brackets)`[` `]` 成對包住所有元素。 `list` 可以是巢狀多維度的,同一個 `list` 中也可以存放異質類型資料,不過一般使用情境還是以同類型的資料較適合。 # # 元素內容是按照儲存順序的 index 存取,語法為 **`[ index ]`**。 如果按照由前往後的順序,**第一個元素 index 是0**,依次往後遞增; 如果反過來由後往前存取,**最後一個元素 index 可以用-1**,依次向前遞減。 # # | List 範例 | 說明 | # |------------------------------------|-----------------------------------------------| # | `[]` | 空的 list | # | `[5, 6, 7, 8]` | 四個數字元素的 list | # | `['code', [42, 3.1415], 1.23, {}]` | 巢狀、異質的 list | # # - 內建函式 `list()` 可以用來建構一個新的`list`物件。 # - 內建函式 `len()` 可以用來回傳容器裡的元素個數。 # - 內建函式 `min()` 可以用來回傳容器中最小的元素。 # - 內建函式 `max()` 可以用來回傳容器中最大的元素。 # # `list` 是序列容器,可以使用序列容器的共通方法(參閱官方文件 [Common Sequence Operations](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations))。 此外,`list` 在建立後,元素內容**可以**就地變更(mutable),標準函式庫另外還有提供可以就地變更的方法,請參閱官方文件 [Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#typesseq-mutable),以下是幾個常用的方法: # - `append()` 追加一個元素在容器後面。 # - `extend()` 追加一系列的元素在容器後面。 # - `del L[m:n]` 刪除範圍內的元素,與 `L[m:n] = []` 相同。 # - `copy()` 產生一份複製,與 `L[:]` 相同。 # - `clear()` 移除所有的元素,與 `del L[:]` 相同。 # - `insert()` 插入元素到某個位置。 # - `remove()` 移除第一個出現的指定元素值。 # - `pop()` 回傳某個位置的元素值,並從容器中移除。 # - `sort()` 對元素就地排序。 # - `reverse()` 就地反轉元素順序。 # # ### § `list` 是可以 In-Place 就地變更的序列容器 # # In[1]: L = [123, [4, 56], 'One-Two-Three', 7.89] print('L = {}.'.format(L)) # In[2]: # a += b 等同於 a = a + b L[0] += 12 print('L = {}, 1st element is changed.'.format(L)) # In[3]: # a -= b 等同於 a = a - b L[1][1] -= 34 print('L = {}, 2nd element is changed.'.format(L)) # In[4]: # a *= b 等同於 a = a * b L[2] *= 2 print('L = {}, 3rd element is changed.'.format(L)) # In[5]: # a /= b 等同於 a = a / b L[3] /= 0.56 print('L = {}, 4th element is changed.'.format(L)) # ### § Slices 片段也可以就地變更 # In[6]: letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] print('letters = {}, {} elements.'.format(letters, len(letters))) # In[7]: # 將序號 2 到 4 的元素分別用新的數值取代 letters[2:5] = [ord('c'), ord('d'), ord('e')] print('letters = {}, {} elements.'.format(letters, len(letters))) # In[8]: # 將序號 2 到 4 的元素全部用一個新的值取代 letters[2:5] = 'X' print('letters = {}, {} elements.'.format(letters, len(letters))) # In[9]: # 將序號 2 到 4 的元素刪除 letters[2:5] = [] print('letters = {}, {} elements.'.format(letters, len(letters))) # In[10]: # 可以用空的 [] 來清空 list letters[:] = [] print('letters = {}, {} elements.'.format(letters, len(letters))) # ### § 讀取的索引值還是不能超過範圍,但寫入和 Slice 的索引範圍可以。 # In[11]: # 讀取超過範圍的索引會出現 IndexError print(L[4]) # In[12]: # 但是 Slice 範圍超過只會被默默忽略 print('L = {}.'.format(L[:10])) print('L reversed = {}.'.format(L[-1:-9:-1])) # In[13]: # 寫入新的 list 物件到索引結束的後面,可以直接追加元素進去 L[len(L):] = list(range(2)) print('L extended = {}, now length = {}.'.format(L, len(L))) # In[14]: # 寫入的 slice 索引超過結束的後面,一樣被忽略 L[len(L) + 2:] = [3, 4] print('L extended = {}, now length = {}.'.format(L, len(L))) # In[15]: # 寫入的 slice 索引橫跨原本有和沒有的範圍,則原本有的會被覆蓋,原本沒有的範圍會新增 L[-2:] = [2, 3, 4, 5] print('L extended = {}, now length = {}.'.format(L, len(L))) # ### § 使用 `list` 物件的方法 (Methods) 來操作 # 針對新增、刪除、插入等 in-place 變更的操作,原則上會建議使用 `list` 物件提供的方法,這樣會使得程式碼可讀性比較高。 # In[16]: # 刪除所有元素 L.clear() print('L = {}, length = {}'.format(L, len(L))) # In[17]: # 新增一系列元素 L.extend(range(3)) print('L = {}, length = {}'.format(L, len(L))) # In[18]: # 新增一個元素,注意和 extend() 方法有甚麼差異 L.append(list(range(3))) print('L = {}, length = {}'.format(L, len(L))) # In[19]: # 刪除最後一個元素 del L[-1:] print('L = {}, length = {}'.format(L, len(L))) # In[20]: # 複製三份 L 串成新的 list 物件,注意三份都是同一個物件的參考 L2 = [L] * 3 print('\nL2 = {},\n(L2[0], L2[1], L2[2]) 和 L 是同一份參考嗎? ({}, {}, {})' .format(L2, L2[0] is L, L2[1] is L, L2[2] is L)) # In[21]: # copy() 是所謂的 shallow copy L2copy = L2.copy() print('\nL2copy = {},\nL2copy 和 L2 是同一份參考嗎? ({}),\nL2copy[0] 和 L 是同一份參考嗎? ({})' .format(L2copy, L2copy is L2, L2copy[0] is L)) # In[22]: # 既然都參考到同一個物件,有一個內容改變了,其他也會跟著變 L.insert(2, 0.5) L.append(1.5) print('\nL = {}\nL2 = {}\nL2copy = {}\n'.format(L, L2, L2copy)) # List 有提供 in-place 排序的方法 `sort()`。另外 Python 也有一個內建函式 `sorted()` 可以用來排序,這個內建函式不是 in-place 排序,但通用於所有支援迭代(iterator)的物件。 # In[23]: # 將元素內容排序 L.sort() print('由小到大排序 L = {}'.format(L)) # In[24]: L.sort(reverse=True) print('由大到小排序 L = {}'.format(L)) # In[25]: # Python 內建函式 sorted 是回傳一個新的 list 物件,不是 in-place 排序。 print('L 的內容由小到大排序,新的 list = {},原本的 L = {} 沒變'.format(sorted(L), L)) # ## 5.1 List Comprehension # # 對於序列容器或可迭代物件 S 進行操作,並生成一個新的 `list` 物件。 # # | 成員的操作 | 說明 | # |-----------------------------------|-------------------------------------------------------------------| # | `[運算表示句 for x in S]` | 針對每個 S 的成員 x 做運算,運算結果生成新的 list 物件 | # | `[運算表示句 for x in S if 條件]` | 針對每個***符合條件***的成員 x 做運算,運算結果生成新的 list 物件 | # # List comprehension 語法結構可組成相當豐富的條件式迭代運算 # ``` # [運算表示句 for x1 in S1 if 條件1 # for x2 in S2 if 條件2 ... # for xN in SN if 條件N] # ``` # In[26]: # 使用 for 迴圈操作 List 容器裡的成員 L = [1, 2, 3, 4, 5] for i in range(len(L)): L[i] += 10 print(L) # 使用 List Comprehension 生成新的 List 物件 L = [x + 10 for x in L] print(L) # In[27]: # 條件式挑選部份成員作處理 L = list(range(10)) Lnew = [n*2 for n in L if n % 2 == 0] Lnew # In[28]: # 巢狀迴圈 + 條件式 L1 = list(range(1, 4)) L2 = list(range(3, 6)) # 一般的巢狀迴圈寫法落落長 #Lnew = [] #for x in L1: # for y in L2: # if x != y: # Lnew.append((x, y)) # List comprehension 的巢狀迴圈 Lnew = [(x, y) for x in L1 for y in L2 if x != y] Lnew # ### § 與其他方法比較 # # + List comprehension 與生成運算表示的語法,除了括號的不同以外其他幾乎都一樣。 但生成運算表示只返回迭代子,不需要在記憶體中生成所有成員再來迭代運算,在大量運算時會比較節省記憶體空間。 # + 內建函式 `map(function, iterable)` 是一個生成函式,返回一個尋訪參數2的可迭代物件,每次的迭代都返回指定函式(參數1)循序套用到尋訪參數2成員的結果。 # + 內建函式 `filter(function, iterable)` 是一個生成函式,返回一個尋訪參數2的可迭代物件,每次迭代只會返回符合函式(參數1)測試結果的參數2的成員。 # + `functools` 模組裡的 `reduce(function, iterable[, initializer])` 累進式套用兩個參數的函式(或二元運算子)到序列的每個成員,一直到序列化簡成單一值為止。 # # 由於 `map`, `filter`, 以及 `reduce` 都需要套用函式呼叫,時常需要另外定義額外的 `lambda` 函式來輔助。 # # 一般而言,使用 `map`, `filter` 的程式效能通常比 `for` 迴圈快;而使用 list comprehension 通常又比 `map`, `filter` 還快。 # In[29]: L = ['31', '0.3', '52', '46.5', '12', '94.7'] # 使用 List comprehension 將字串清單轉成數值清單 print([float(s) for s in L]) # 使用生成運算表示將字串清單轉成數值清單 print(list(float(s) for s in L)) # 使用 map() 將字串清單轉成數值清單 print(list(map(float, L))) # In[30]: # 使用 List comprehension 僅將清單裡的十進位整數字串轉成數值 print([int(s) for s in L if s.isdecimal()]) # 使用生成運算表示將清單裡的十進位整數字串轉成數值 print(list(int(s) for s in L if s.isdecimal())) # 使用 map() + filter() 將清單裡的十進位整數字串轉成數值 print(list(map(int, filter(str.isdecimal, L)))) # In[31]: # List comprehension 要另外靠函式來達到 reduce 相同的效果 print(sum([float(s) for s in L])) # 生成運算表示也要另外靠函式來達到 reduce 相同的效果 print(sum(float(s) for s in L)) # 使用 functools.reduce() 將清單裡的字串數值加總 import operator, functools print(functools.reduce(operator.add, [float(s) for s in L])) # operator.add 改用自定義 lambda print(functools.reduce((lambda x, y: x + y), (float(s) for s in L)))