教程 | Python集合與集合運算
機器之心編譯
了解 Python 集合: 它們是什麼,如何創建它們,何時使用它們,什麼是內置函數,以及它們與集合論操作的關係
集合、 列表與元組
列表(list)和元組(tuple)是標準的 Python 數據類型,它們將值存儲在一個序列中。集合(set)是另一種標準的 Python 數據類型,它也可用於存儲值。它們之間主要的區別在於,集合不同於列表或元組,集合中的每一個元素不能出現多次,並且是無序存儲的。
Python 集合的優勢
由於集合中的元素不能出現多次,這使得集合在很大程度上能夠高效地從列表或元組中刪除重複值,並執行取並集、交集等常見的的數學操作。
本教程將向你介紹一些關於 Python 集合和集合論的話題:
如何初始化空集和帶有數值的集合
如何向集合中添加值或者從集合中刪除值
如何高效地使用集合,用於成員檢測、從列表中刪除重複值等任務。
如何執行常見的集合操作,例如求並集、交集、差集以及對稱差。
可變集合和不可變集之間的區別
有了這個提綱,讓我們開始吧。
集合初始化
集合是一個擁有確定(唯一)的、不變的的元素,且元素無序的可變的數據組織形式。
你可以使用「set()」操作初始化一個空集。
emptySet = set()
如果要初始化一個帶有值的集合,你可以向「set()」傳入一個列表。
dataScientist = set([
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,
"SAS"
])dataEngineer = set([
"Python"
,"Java"
,"Scala"
,"Git"
,"SQL"
,"Hadoop"
])如果你觀察一下上面的「dataScientist」和「dataEngineer」集合中的變數,就會發現集合中元素值的順序與添加時的順序是不同的,這是因為集合是無序的。
集合包含的值也可以通過花括弧來初始化。
dataScientist = {
"Python"
,"R"
,"SQL"
,"Git"
,
"Tableau"
,"SAS"
}dataEngineer = {
"Python"
,"Java"
,"Scala"
,"Git"
,"SQL"
,"Hadoop"
}請牢記,花括弧只能用於初始化包含值的集合。如下圖所示,使用不包含值的花括弧是初始化字典(dict)的方法之一,而不是初始化集合的方法。
向集合添加值或刪除值
要想向集合中添加值或從中刪除值,你首先必須初始化一個集合。
# Initialize set
with
valuesgraphicDesigner = {
"InDesign"
,"Photoshop"
,"Acrobat"
,"Premiere"
,"Bridge"
}向集合中添加值
你可以使用「add」方法向集合中添加一個值。
graphicDesigner.add(
"Illustrator"
)需要注意的一點是,你只能將不可變的值(例如一個字元串或一個元組)加入到集合中。舉例而言,如果你試圖將一個列表(list)添加到集合中,系統會返回類型錯誤「TyprError」。
graphicDesigner.add([
"Powerpoint"
,"Blender"
])從集合中刪除值
有好幾種方法可以從集合中刪除一個值:
選項 1
:你可以使用「remove」方法從集合中刪除一個值。
graphicDesigner.remove(
"Illustrator"
)這種方法的一個缺點是,如果你想要刪除一個集合中不存在的值,系統會返回一個鍵值錯誤「KeyError」。
選項 2:
你可以使用「discard」方法從集合中刪除一個值。
graphicDesigner.discard(
"Premiere"
)這種方法相對於「remove」方法的好處是,如果你試圖刪除一個集合中不存在的值,系統不會返回「KeyError」。如果你熟悉字典(dict)數據結構,你可能會發現這種方法與字典的「get」方法的工作模式相似。
選項 3:你還可以使用「pop」方法從集合中刪除並且返回一個任意的值。
graphicDesigner.pop()
需要注意的是,如果集合是空的,該方法會返回一個「KeyError」。
刪除集合中所有的值
你可以使用「clear」方法刪除集合中所有的值。
graphicDesigner.clear()
在集合上進行迭代
與許多標準 Python 數據類型一樣,用戶可以在集合(set)上進行迭代。
# Initialize a set
dataScientist = {
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,"SAS"
}for
skillin
dataScientist:print(skill)
如果你仔細觀察「dataScientist」集合中列印出來的每一個值,你會發現集合中的值被列印出來的順序與它們被添加的順序是不同的。
將集合中的值變為有序
本教程已經向大家強調了集合是無序的。如果你認為你需要以有序的形式從集合中取出值,你可以使用「sorted」函數,它會輸出一個有序的列表。
type(sorted(dataScientist))
下面的代碼按照字母降序(這裡指 Z-A)輸出「dataScientist」集合中的值。
sorted(dataScientist, reverse = True)
刪除列表中的重複項
首先我們必須強調的是,集合是從列表(list)中刪除重複值的最快的方法。為了證明這一點,讓我們研究以下兩種方法之間的差異。
方法 1:
使用集合刪除列表中的重複值。
print(list(set([
1
,2
,3
,1
,7
])))方法 2:使用一個列表推導式(list comprehension)從一個列表中刪除重複值。
def remove_duplicates(original):
unique = []
[unique.append(n)
for
nin
originalif
n notin
unique]return
(unique)print(remove_duplicates([
1
,2
,3
,1
,7
]))性能的差異可以用「timeit」庫來測量,這個庫允許你對 Python 代碼進行計時。下面的代碼將每種方法運行了 10,000 次,並且以秒為單位輸出了總計時間。
import
timeit# Approach
1
: Execution timeprint(timeit.timeit(
"list(set([1, 2, 3, 1, 7]))"
, number=10000
))# Approach
2
: Execution timeprint(timeit.timeit(
"remove_duplicates([1, 2, 3, 1, 7])"
, globals=globals(), number=10000
))對比這兩種方法,結果表明,使用集合刪除重複值是更加高效的。雖然時間差異看似很小,但實際上在有一個非常大的列表時,能幫你節省很多的時間。
集合運算方法
Python 中常用的集合方法是執行標準的數學運算,例如:求並集、交集、差集以及對稱差。下圖顯示了一些在集合 A 和集合 B 上進行的標準數學運算。每個韋恩(Venn)圖中的紅色部分是給定集合運算得到的結果。
Python 集合有一些讓你能夠執行這些數學運算的方法,還有一些給你等價結果的運算符。在研究這些方法之前,讓我們首先初始化「dataScientist」和「dataEngineer」這兩個集合。
dataScientist = set([
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,"SAS"
])dataEngineer = set([
"Python"
,"Java"
,"Scala"
,"Git"
,"SQL"
,"Hadoop"
])並集
一個表示為「dataScientist ∪ dataEngineer」的並集,是屬於「dataScientist」或「dataEngineer」或同時屬於二者元素的集合。你可以使用「union」方法找出兩個集合中所有唯一的值。
# set built-
in
function
union
dataScientist
.union
(dataEngineer
)#
Equivalent
Result
dataScientist
|dataEngineer
求並集操作返回的集合可以被可視化為下面的韋恩(Venn)圖中的紅色部分。
交集
集合「dataScientist」和「dataEngineer」的交集可以表示為「dataScientist ∩ dataEngineer」,是所有同時屬於兩個集合的元素集合。
# Intersection operation
dataScientist.intersection(dataEngineer)
# Equivalent Result
dataScientist & dataEngineer
交集運算返回的集合可以被可視化為下面韋恩圖中的紅色部分。
你可能會發現,你會遇到你想確保兩個集合沒有共同值的情況。換句話說,你想得到兩個交集為空的集合。這兩個集合稱為互斥集合,你可以使用「isdisjoint」方法測試兩個集合是否為互斥。
# Initialize a set
graphicDesigner = {
"Illustrator"
,"InDesign"
,"Photoshop"
}# These sets have elements
in
common so it wouldreturn
FalsedataScientist.isdisjoint(dataEngineer)
# These sets have no elements
in
common so it wouldreturn
TruedataScientist.isdisjoint(graphicDesigner)
你會注意到,在如下韋恩圖所示的交集中,「dataScientist」和「graphicDesigner」沒有共有的值。
差集
集合「dataScientist」和「dataEngineer」的差集可以表示為「dataScientist dataEngineer」,是所有屬於「dataScientist」但不屬於「dataEngineer」的元素集合。
# Difference Operation
dataScientist.difference(dataEngineer)
# Equivalent Result
dataScientist - dataEngineer
差集運算返回的結果可以被可視化為以下韋恩圖中的紅色部分。
對稱集
一個「dataScientist」和「dataEngineer」的對稱集,表示為「dataScientist △ dataEngineer」,它是所有屬於兩個集合但不屬於二者共有部分的集合。
# Symmetric Difference Operation
dataScientist.symmetric_difference(dataEngineer)
# Equivalent Result
dataScientist ^ dataEngineer
對稱集運算返回的結果可以被可視化為下面韋恩圖中的紅色部分。
集合推導式
你之前可能已經學習過列表推導式(list comprehensions)、字典推導式(dictionary comprehensions)和生成器推導式。這裡還有一個集合推導式(Set Comprehension)。集合推導式和它們是很類似的,Python 中的集合推導式可以按照下面的方法構造:
{skill
for
skillin
["SQL"
,"SQL"
,"PYTHON"
,"PYTHON"
]}上面的輸出為一個包含 2 個值的集合,因為集合中相同的元素不能多次出現。使用集合推導式背後的動機是希望能夠用手動進行數學運算的方法在代碼中編寫和推導式子。
{skill
for
skillin
["GIT"
,"PYTHON"
,"SQL"
]if
skill notin
{"GIT"
,"PYTHON"
,"JAVA"
}}上面的代碼與你之前學過的求差集類似,只是看上去有一點點不同。
成員檢測
成員檢測能夠檢查某個特定的元素是否被包含在一個序列中,例如字元串、列表、元組或集合。在 Python 中使用集合的一個主要的優點是,它們在 Python 中為成員檢測做了深度的優化。例如,對集合做成員檢測比對列表做成員檢測高效地多。如果你是計算機科班出身,我們可以說,這是因為集合中成員檢測的平均時間複雜度是 O(1)的而列表中則是 O(n)。
下面的代碼展示了使用列表做成員檢測的過程:
# Initialize a list
possibleList = [
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,"SAS"
,"Java"
,"Spark"
,"Scala"
]# Membership test
"Python"
in
possibleList集合中也可以做類似的操作,只不過集合更加高效。
# Initialize a set
possibleSet = {
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,"SAS"
,"Java"
,"Spark"
,"Scala"
}# Membership test
"Python"
in
possibleSet由於「possibleSet」是一個集合,而且「Python」是集合「possibleSet」中的一個元素,這可以被表示為「Python" ∈ possibleSet」如果你有一個不屬於集合的值,比如「Fortran」,這可以被表示為「Fortran" ? possibleSet」。
子集
實際上集合的成員及成員的組合就是一個子集,讓我們首先初始化兩個集合。
possibleSkills = {
"Python"
,"R"
,"SQL"
,"Git"
,"Tableau"
,"SAS"
}mySkills = {
"Python"
,"R"
}如果集合「mySkills」中的每一個值都屬於集合「possibleSkills」,那麼「mySkills」被稱為「possibleSkills」的一個子集,數學上寫作「mySkills ? possibleSkills」。你可以使用「issubset」方法檢查一個集合是否是另一個集合的子集。
mySkills.issubset(possibleSkills)
因為在這個例子中,這個方法返回的是「True」。在下面的韋恩圖中,請注意「mySkills」中的每一個值同時也在集合「possibleSkills」中。
不可變集
我們常常能看到嵌套的列表或元組,它們的元素可能是另一個列表或元組。
# Nested Lists and Tuples
nestedLists = [[
"the"
,12
], ["to"
,11
], ["of"
,9
], ["and"
,7
], ["that"
,6
]]nestedTuples = ((
"the"
,12
), ("to"
,11
), ("of"
,9
), ("and"
,7
), ("that"
,6
))嵌套集合的問題在於,集合中通常不能包含集合等可變的值。在這種情況下,你可能希望使用一個不可變集(frozenset)。除了值不可以改變,不可變集和可變集是很相似的。你可以使用「frozenset()」創建一個不可變集。
# Initialize a frozenset
immutableSet = frozenset()
如果你使用如下所示的不可變集,就可以創建一個嵌套集合了。
nestedSets = set([frozenset()])
重要的是,你需要記住,不可變集的一個主要的缺點是:由於它們是不可變的,這意味著你不能向其中添加元素或者刪除其中的元素。
結語
Python 集合是非常實用的,它能夠高效地從列表等數據結構中刪除重複的值,並且執行常見的數學運算,例如:求並集、交集。人們經常遇到的一個挑戰是:何時使用各種數據類型,例如什麼時候使用集合或字典。作者希望本文能展示基本的集合概念,並有利於我們在不同任務中使用不同的數據類型。
推薦閱讀
收藏 | 白話Hadoop架構原理
下載 | 512頁教程《神經網路與深度學習》,2018最新著作
必備 | AI & DS七大 Python 庫
下載 | 954頁《數據可視化》手冊
知識點 | 全面理解支持向量機