在 Python 我們有時候會需要作出一些比較數據的任務。
例如我們有 2 組名單(A 和 B),裡面的人名可以出現在 A、B、或者同時出現在 A 及 B,那麼我們可以透過簡單的 Python 編程找出「同時出現在 A 及 B」的群組。
又例如我們有 2 張列表,一張是數據處理前、一張是處理後,那麼我們透過這些方法亦能找出經過處理的列表成員,甚至還原數據的處理!
核心問題
開始編程前,我們先要問一下自己想要解決的問題是什麼。這次我們希望透過比較 2 張列表:
- 找出同時存在於兩張列表的項目
- 找出兩張列表的不同
- 分析兩張列表的分別
這次教學我們會頻繁使用 Python 獨特的 List Comprehension,如果您不太熟悉記得按一下這篇教學:List Comprehension: Python 的 For Loop 怎樣使用?
事不宜遲,我們立刻開始學習吧!
第 1 題:找出同時存在於兩張列表的項目
list_1 = ['A','B','C','D','E','F','G','H','I']
list_2 = ['G','H','I','J','K','L','M']
intersection = [x for x in list_1 for y in list_2 if x == y]
print(intersection)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
我們從最簡單的例子開始吧。假如我們有兩張有關英文字母的列表,而第一個列表有 A 至 I、第二個列表有 G 至 M,那麼我們的編程需要回傳 G、H、I 作為同時存於兩張列表的項目。
留意我們這兩張列表的長度不一,第一張列表比第二張長。
以下簡單的 list comprehension 可以直接回傳我們所需。我們先稱呼這張回傳的列表為「intersection」。
intersection = [x for x in list_1 for y in list_2 if x == y]
這裡略略介紹一下這個寫法的運作方法。List comprehension 基本上就是一個 for loop,而這裡留意我們同時有 for x in list_1
和 for y in list_2
這 2 個 for loop。
因此,我們在 Loop 到每一個 list_1
的項目(A、B、…)是,檢查一次這個項目是否存在於 list_2
裡(if x == y
)。
其實除了以上寫法之外,我們亦可以這樣寫:
intersection = [y for x in list_1 for y in list_2 if x == y]
由於 intersection 的項目必須同時存於 list_1
和 list_2
,所以無論我們用 x 或 y 作為回傳的子項目,回傳的 intersection 都是一樣的。
小插曲:如果我們的列表有重複出現的項目?
list_1 = ['A','B','C','D','E','F','G','H','H','I']
list_2 = ['G','H','I','J','K','L','M','M']
intersection = [x for x in list_1 for y in list_2 if x == y]
print('如果沒有用 set():', intersection)
print('如果用 set():', list(set(intersection)))
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
如果我們在剛才的示範裡加入在同一個列表出現多次的項目(例如 H),那麼以上面的方法計算出來的 intersection 便會出現多次該項目。
這個用法讓我們容易找出每個同時於兩張列表出現的項目,最高出現的頻率。例如 H 在 list_1
出現 2 次,在 list_2
出現 1 次,那麼算出來的 intersection 便會有 2 個 H。
但如果我們只需要知道哪些項目同時在兩張列表出現,但我們不太管頻率,那麼我們可以透過這個代碼獲取不重複(unique)的項目:
list(set(intersection))
set()
基本上就像是 Excel 移除重複項(Remove Duplicates)的 Python 版。而我們要加以 list()
,把這個輸出還原成為一個列表。
第 2 題:找出兩張列表的不同
這個比剛才的問題稍為複雜一點,因為我們需要考量的是我們需要什麼樣的不同。
list_1 = ['A','B','C','D','E','F','G','H','H','I']
list_2 = ['G','H','I','J','K','L','M','M']
difference_in_1_not_in_2 = [x for x in list_1 if x not in list_2]
difference_in_2_not_in_1 = [x for x in list_2 if x not in list_1]
print('在 list_1 而不在 list_2:', difference_in_1_not_in_2)
print('在 list_2 而不在 list_1:', difference_in_2_not_in_1)
print('全部不同:', difference_in_1_not_in_2 + difference_in_2_not_in_1)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
如果我們比較兩個 list,首先我們需要選擇以哪一個列表作為我們的基準。就如以上例子,我們的 difference_in_1_not_in_2
和 difference_in_2_not_in_1
可是兩個截然不同的回傳!
如果我們以 list_1
為基準,找出在 list_2
但不在 list_1
裡的項目,語法是:
difference_in_1_not_in_2 = [x for x in list_1 if x not in list_2]
那麼,如果我們想要獲取兩個列表不同的所有元素(即是 intersection 的相反),那麼我們可以直接把
和 difference_in_1_not_in_2
相加。上述編程例子中,difference_in_2_not_in_1
difference_all
就是蘊含了這些元素的回傳。
difference_all = difference_in_1_not_in_2 + difference_in_2_not_in_1
相加後,由於
和 difference_in_1_not_in_2
是互相排擠(Mutually Exclusive)的,所以我們不會得到重複的元素。difference_in_2_not_in_1
當然,正如上面的小插曲提及,如果 list_1
和 list_2
本身有重複出現的項目,而您只需要知道哪些項目重複,您可以使用 list(set(difference_all))
的方法回傳不重複(unique)的項目。
第 3 題:分析兩張列表的分別
最後我們來談談如何簡單分析兩張列表的分別。有時候我們想要還原一些數據處理的結果。我們來先看看一個例子:
list_1 = ['Chan, Tai Man', 'Chan, Siu Wan', 'Cheung, Ho Man', 'Fung, Ka Yi', 'Lam, Yuen Ho', 'Tse, Lo Si']
list_2 = ['Chan, Tai Man', 'Chan, Siu Wan Ashley', 'Cheung, Ho Man', 'Fung, Ka Yi Karmen', 'Lam, Yuen Ho Ben', 'Tse, Lo Si']
print(list(zip(list_1,list_2)))
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
譬如這個例子我們有兩張同一個順序的人名,但差別在於 list_1
只有中文名的羅馬拼音,而 list_2
卻多添了英文名稱(如那人有)。
我們的任務是還原這個數據處理,而我們希望 Python 回傳一個字典(Dictionary),方便我們對應中文名的羅馬拼音與英文名稱。
留意 zip()
這個功能是把兩張列表的合併成一張,而每一個項目是原來列表的值,以元組(tuple)儲存。前提是我們這兩張列表有一樣數量的項目。
list_1 = ['Chan, Tai Man', 'Chan, Siu Wan', 'Cheung, Ho Man', 'Fung, Ka Yi', 'Lam, Yuen Ho', 'Tse, Lo Si']
list_2 = ['Chan, Tai Man', 'Chan, Siu Wan Ashley', 'Cheung, Ho Man', 'Fung, Ka Yi Karmen', 'Lam, Yuen Ho Ben', 'Tse, Lo Si']
map_name = {x[0]: x[1].split()[-1] for x in list(zip(list_1,list_2)) if x[0] != x[1]}
print(map_name)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
這個 map_name
的 List Comprehension 有點複雜。
我們的字典(Dictionary)的 Key 是 x[0]
,即是 zip tuple 裡的第一個元素。而我們 zip 的順序是 list_1
先到 list_2
,所以第一個元素便是 list_1
的元素,即是沒有英文名稱的元素。
字典的 Value 是 x[1].split()[-1]
。x[1]
是我們含有英文名稱的元素(來自 list_2
),我們通過提取名稱最後一個字(.split()[-1]
)獲取對應該人的英文名稱。
但是,對於沒有英文名稱的人,我們不想把他們加入到字典裡,所以我們最後加入 if x[0] != x[1]
的語法,確保回傳的字典只會是有英文名稱的人。
現在我們可以拿著 map_name
去告訴別人我們的英文名稱是什麼了!
這個語法的偽代碼(pseudo-code)如下:
{x[0]: some_function(x[1]) for x in list(zip(list_1, list_2)) if x[0] != x[1]}
教學完整代碼
最後送給大家這篇教學的 Google Colab 完整代碼。如果您不懂得使用免安裝又好用的 Google Colab Notebook,記得閱讀這篇教學了:新手 1/3:5 分鐘免安裝學習 Python?Google Colab Notebook 幫緊您!
結語
當然,比較兩張列表的方法有許多,我們不能盡錄,但希望以上例子讓您學會更多有關 List Comprehension 的多變和強大的用途!