跳到主要内容 Python 核心数据结构:集合与冻结集合 | 极客日志
Python 算法
Python 核心数据结构:集合与冻结集合 Python 集合是元素唯一且无序的数据结构,支持高效的数学运算如并集、交集、差集等。本文介绍集合的创建、增删改查及成员检查操作,重点讲解不可变的冻结集合及其作为字典键的用途。通过去重、多数据源对比、文本停用词过滤及社交网络兴趣分析等实战案例,展示集合在数据处理中的高效性与实用性。
第 4 章:无序且唯一的集合:集合与冻结集合
章节介绍
Python 中,除了列表和元组这类有序的序列,还有一类非常实用的无序容器:集合。集合最核心的特征是它的元素是唯一且无序 的。想象一下,当你需要记录一批用户的唯一标签,或者快速比对两份数据之间的差异时,集合就能大显身手。它与数学中的集合概念高度一致,支持交集、并集等运算,处理这类问题既直观又高效。
创建一个集合很简单,可以直接用花括号 {},或者使用 set() 函数。但更常见的情况是,我们从已有的数据(比如一个可能包含重复项的列表)中提取唯一元素。这时,集合的'唯一性'就派上了用场。
def create_set_from_list (data_list: list ) -> set :
""" 从给定的列表创建一个集合。
集合会自动去除列表中的重复元素,并失去原有的顺序。
这是演示集合创建和其'唯一性'核心特性的基本示例。
参数:
data_list (list): 可能包含重复元素的输入列表。
返回:
set: 由输入列表元素构成的新集合。
"""
result_set = set (data_list)
return result_set
来轻松实现这一点,它会自动滤掉所有重复的内容。让我们看一个更直观的例子:
def demonstrate_set_uniqueness ():
""" 展示集合的核心特性:无序且唯一。
通过对比转换前后的数据,直观显示重复元素如何被自动去除。
"""
my_list = [1 , 2 , 2 , 3 , 4 , 4 , 4 , 5 , 'apple' , 'banana' , 'apple' ]
print ("原始列表(可能包含重复项):" )
print (f" {my_list} " )
print (f" 列表长度:{len (my_list)} " )
my_set = set (my_list)
print ("\n转换后的集合(自动去重):" )
( )
( )
( )
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
print
f" {my_set} "
print
f" 集合长度:{len (my_set)} "
print
"\n结论:集合自动删除了所有重复的 '2', '4' 和 'apple'。"
运行后你会发现,一个满是重复数字的列表,在转换为集合后,每个数字只保留了一个。
集合是可变的,这意味着你可以随时向其中添加或删除元素。
def basic_set_operations (initial_set: set , element_to_add, element_to_remove ):
""" 在一个初始集合上执行一系列基础操作。
这些操作是使用集合数据结构时最常见的。
参数:
initial_set (set): 初始的集合。
element_to_add: 要添加到集合中的元素。
element_to_remove: 要从集合中尝试移除的元素。
返回:
set: 执行操作后最终得到的集合。
"""
working_set = initial_set.copy()
print (f"初始集合:{working_set} " )
print (f"\n1. 添加元素 '{element_to_add} '..." )
working_set.add(element_to_add)
print (f" 添加后:{working_set} " )
print (f"\n2. 尝试移除元素 '{element_to_remove} '..." )
if element_to_remove in working_set:
working_set.remove(element_to_remove)
print (f" 成功移除。移除后:{working_set} " )
else :
print (f" 元素 '{element_to_remove} ' 不在集合中,移除失败。" )
print (f"\n3. 使用 discard() 安全移除 '{element_to_remove} ' (如果存在)..." )
working_set.discard(element_to_remove)
print (f" 操作后:{working_set} " )
test_element = element_to_add
print (f"\n4. 检查元素 '{test_element} ' 是否在集合中..." )
is_present = test_element in working_set
print (f" 结果:{is_present} " )
print (f"\n5. 清空集合..." )
working_set.clear()
print (f" 清空后:{working_set} " )
return working_set
展示了如何进行这些基础操作,比如添加一个元素、移除一个元素、检查某个元素是否存在,或者清空整个集合。这些操作让你能动态地管理一个不重复的数据池。
集合真正的威力在于它的数学运算。假设你有两个集合,A 代表小明喜欢的电影,B 代表小红喜欢的电影。如何找出他们共同喜欢的电影(交集)?或者他们所有喜欢的电影合集(并集)?Python 为集合提供了非常直观的运算符和方法。
def perform_set_union (set_a: set , set_b: set ) -> set :
""" 计算并返回两个集合的并集。
并集包含所有属于 set_a 或 set_b 的元素。
参数:
set_a (set): 第一个集合。
set_b (set): 第二个集合。
返回:
set: set_a 和 set_b 的并集。
"""
union_by_method = set_a.union(set_b)
union_by_operator = set_a | set_b
print (f"集合 A: {set_a} " )
print (f"集合 B: {set_b} " )
print (f"并集 (使用 union()): {union_by_method} " )
print (f"并集 (使用 | 运算符): {union_by_operator} " )
print (f"两种方法结果一致吗? {union_by_method == union_by_operator} " )
return union_by_method
def perform_set_intersection (set_a: set , set_b: set ) -> set :
""" 计算并返回两个集合的交集。
交集包含所有同时属于 set_a 和 set_b 的元素。
参数:
set_a (set): 第一个集合。
set_b (set): 第二个集合。
返回:
set: set_a 和 set_b 的交集。
"""
intersection_by_method = set_a.intersection(set_b)
intersection_by_operator = set_a & set_b
print (f"集合 A: {set_a} " )
print (f"集合 B: {set_b} " )
print (f"交集 (使用 intersection()): {intersection_by_method} " )
print (f"交集 (使用 & 运算符): {intersection_by_operator} " )
return intersection_by_method
展示了如何得到交集。你还可以找出小明喜欢但小红不喜欢的电影(差集),通过
def perform_set_difference (set_a: set , set_b: set ) -> set :
""" 计算并返回两个集合的差集 (A - B)。
差集包含所有属于 set_a 但不属于 set_b 的元素。
注意:差集运算不具有交换律,A - B 与 B - A 不同。
参数:
set_a (set): 被减集合。
set_b (set): 减去的集合。
返回:
set: set_a 和 set_b 的差集 (A - B)。
"""
difference_ab_method = set_a.difference(set_b)
difference_ab_operator = set_a - set_b
difference_ba = set_b - set_a
print (f"集合 A: {set_a} " )
print (f"集合 B: {set_b} " )
print (f"差集 A - B (使用 difference()): {difference_ab_method} " )
print (f"差集 A - B (使用 - 运算符): {difference_ab_operator} " )
print (f"差集 B - A (作为对比): {difference_ba} " )
print (f"A - B 等于 B - A 吗? {difference_ab_method == difference_ba} " )
return difference_ab_method
实现;或者找出他们各自独有的电影(对称差集),通过
def perform_set_symmetric_difference (set_a: set , set_b: set ) -> set :
""" 计算并返回两个集合的对称差集。
对称差集包含所有属于 set_a 或 set_b,但不同时属于两者的元素。
可以理解为 (A | B) - (A & B)。
参数:
set_a (set): 第一个集合。
set_b (set): 第二个集合。
返回:
set: set_a 和 set_b 的对称差集。
"""
sym_diff_by_method = set_a.symmetric_difference(set_b)
sym_diff_by_operator = set_a ^ set_b
union_set = set_a | set_b
inter_set = set_a & set_b
verification = union_set - inter_set
print (f"集合 A: {set_a} " )
print (f"集合 B: {set_b} " )
print (f"对称差集 (使用 symmetric_difference()): {sym_diff_by_method} " )
print (f"对称差集 (使用 ^ 运算符): {sym_diff_by_operator} " )
print (f"验证 (并集 - 交集): {verification} " )
print (f"结果一致吗? {sym_diff_by_method == verification} " )
return sym_diff_by_method
除了运算,集合间的关系判断也很有用。例如,你想知道一个技能集合是否完全包含在另一个更大的技能要求集合中。
def check_set_relations (set_a: set , set_b: set ) -> dict :
""" 全面检查两个集合之间的所有可能关系。
这对于理解数据间的包含和排斥关系非常有用。
参数:
set_a (set): 第一个集合。
set_b (set): 第二个集合。
返回:
dict: 一个包含所有关系检查结果的字典。
"""
results = {
"A 是 B 的子集 (A ⊆ B)" : set_a.issubset(set_b),
"A 是 B 的真子集 (A ⊂ B)" : set_a.issubset(set_b) and set_a != set_b,
"A 是 B 的超集 (A ⊇ B)" : set_a.issuperset(set_b),
"A 是 B 的真超集 (A ⊃ B)" : set_a.issuperset(set_b) and set_a != set_b,
"A 和 B 相等 (A == B)" : set_a == set_b,
"A 和 B 不相交 (无共同元素)" : set_a.isdisjoint(set_b),
}
print (f"集合 A: {set_a} " )
print (f"集合 B: {set_b} " )
print ("\n关系检查结果:" )
for relation, result in results.items():
print (f" {relation} : {result} " )
return results
可以帮你检查子集、超集以及两个集合是否完全没有共同元素(不相交)等关系。
现在,让我们谈谈集合的一个'兄弟'类型:冻结集合(frozenset) 。它拥有集合的所有特性(唯一、无序),但有一个关键区别:它是不可变 的。就像元组之于列表,冻结集合一旦创建就不能被修改。你可能会问,一个不能变的集合有什么用?最大的用处在于,它可以作为字典的键,或者成为另一个集合的元素,而普通的可变集合是做不到这一点的,因为字典键和集合元素都要求是'可哈希的'不可变对象。
def create_and_use_frozenset (iterable_data ):
""" 创建并使用冻结集合 (frozenset)。
冻结集合是不可变的集合,一旦创建,就不能添加、删除或修改其元素。
它可以用作字典的键或其他集合的元素。
参数:
iterable_data: 可迭代对象,如列表、元组、字符串或另一个集合。
返回:
frozenset: 新创建的冻结集合。
"""
frozen = frozenset (iterable_data)
print (f"创建的冻结集合:{frozen} " )
print (f"类型:{type (frozen)} " )
print (f"\n冻结集合支持的操作(不修改自身):" )
print (f" 长度:{len (frozen)} " )
print (f" 成员检查 ('a' in frozen): {'a' in frozen} " )
if len (frozen) > 0 :
sample_element = next (iter (frozen))
print (f" 迭代(第一个元素): {sample_element} " )
another_set = {1 , 2 , 3 }
print (f"\n与普通集合 {{1, 2, 3}} 进行运算:" )
print (f" 并集 (frozen | another_set): {frozen | another_set} " )
print (f" 交集 (frozen & another_set): {frozen & another_set} " )
print (f"\n尝试修改操作(将导致 AttributeError):" )
try :
print (" frozen.add(99): 不可用" )
except AttributeError as e:
print (f" 错误:{type (e).__name__} : {e} " )
return frozen
def demonstrate_frozenset_as_key ():
""" 演示冻结集合的核心用途之一:作为字典的键。
因为冻结集合是不可变的、可哈希的,所以它可以作为字典的键,
而普通的可变集合(set)则不行。
"""
key1 = frozenset ([1 , 2 , 3 ])
key2 = frozenset (['a' , 'b' ])
key3 = frozenset (['x' , 'y' , 'z' ])
print ("创建的冻结集合键:" )
print (f" key1: {key1} " )
print (f" key2: {key2} " )
print (f" key3: {key3} " )
my_dict = {
key1: "与数字集合相关的值" ,
key2: "与字母集合相关的值" ,
key3: "另一个字母集合的值"
}
print ("\n使用冻结集合作为键的字典:" )
for key, value in my_dict.items():
print (f" {key} -> {value} " )
lookup_key = frozenset ([1 , 2 , 3 ])
print (f"\n查询键 {lookup_key} :" )
if lookup_key in my_dict:
print (f" 找到的值:'{my_dict[lookup_key]} '" )
else :
print (" 未找到该键。" )
print ("\n尝试使用普通集合作为字典键(将失败):" )
try :
bad_dict = {set ([1 , 2 ]): "这将失败" }
except TypeError as e:
print (f" 预期错误:{type (e).__name__} : {e} - 因为集合是不可哈希的。" )
def create_set_of_frozensets (list_of_lists: list [list ] ) -> set :
""" 创建一个集合,其元素是多个冻结集合。
由于冻结集合是不可变的、可哈希的,它们可以作为另一个集合的元素。
这是存储一组唯一'集合组'的有效方式。
参数:
list_of_lists (list[list]): 一个列表,其中每个元素是一个列表(将转换为冻结集合)。
返回:
set: 一个包含冻结集合的集合。
"""
set_of_frozen = set ()
print ("从以下列表创建冻结集合并加入一个总集合中:" )
for i, sublist in enumerate (list_of_lists):
frozen_elem = frozenset (sublist)
print (f" 列表{i+1 } : {sublist} -> 冻结集合:{frozen_elem} " )
set_of_frozen.add(frozen_elem)
print (f"\n最终创建的'冻结集合的集合':" )
print (f" {set_of_frozen} " )
print (f" 类型:{type (set_of_frozen)} " )
print (f" 大小:{len (set_of_frozen)} " )
print ("\n尝试添加一个已存在的冻结集合(内容与列表 1 相同)..." )
duplicate_frozen = frozenset (list_of_lists[0 ])
before_size = len (set_of_frozen)
set_of_frozen.add(duplicate_frozen)
after_size = len (set_of_frozen)
print (f" 添加前集合大小:{before_size} " )
print (f" 添加后集合大小:{after_size} " )
print (f" 是否成功添加? {before_size != after_size} (False 表示因重复而被忽略)" )
return set_of_frozen
展示了如何创建一个包含多个冻结集合的集合,这在需要对多个固定集合进行整体管理时非常有用。
了解了集合和冻结集合的特性后,我们来看看它们在实践中的典型应用。一个最常见的用途就是快速去重。虽然可能会丢失原始顺序,但如果你只关心有哪些不重复的元素,这无疑是最简洁的方法:
def deduplicate_list_using_list (original_list: list ) -> list :
""" 使用集合快速去重。
参数:
original_list (list): 原始列表。
返回:
list: 去重后的列表(顺序可能改变)。
"""
return list (set (original_list))
另一个场景是从多份数据中寻找共性,比如找出多个用户共同的好友。利用集合的交集操作,可以一行代码高效解决:
def find_common_elements_in_multiple_lists (*lists: list ) -> set :
""" 在多个列表中查找共同元素(存在于所有列表中的元素)。
利用集合的`intersection`操作,可以非常高效地解决此问题。
这是数据分析和处理中的常见任务。
参数:
*lists (list): 可变数量的列表参数。
返回:
set: 包含所有输入列表共有元素的集合。如果输入列表少于 2 个,返回空集。
"""
if len (lists) < 2 :
print ("警告:至少需要提供两个列表进行比较。" )
return set ()
print (f"在 {len (lists)} 个列表中查找共同元素:" )
for i, lst in enumerate (lists):
print (f" 列表{i+1 } : {lst} " )
common_elements = set (lists[0 ])
for lst in lists[1 :]:
common_elements = common_elements.intersection(lst)
if not common_elements:
break
print (f"\n所有列表共有的元素是:" )
if common_elements:
print (f" {common_elements} " )
else :
print (" (没有共同元素)" )
return common_elements
在处理文本时,集合也很有优势。例如,我们有一个'停用词'集合(如'的'、'了'、'和'等无实义的词),需要从一段文本中过滤掉它们。由于集合的成员检查速度极快,这个任务变得非常高效:
def filter_text_with_stopwords (text: str , stopwords_set: set ) -> list :
""" 使用停用词集合过滤一段文本。
停用词(如'the', 'is', 'at')通常对文本分析意义不大,需要被移除。
利用集合的 O(1) 平均时间复杂度成员检查,此操作非常高效。
参数:
text (str): 待处理的原始文本字符串。
stopwords_set (set): 停用词集合。
返回:
list: 清理后(不含停用词)的单词列表。
"""
words = text.lower().split()
original_word_count = len (words)
print (f"原始文本:\"{text} \"" )
print (f"原始单词列表 ({original_word_count} 个): {words} " )
print (f"停用词集合 ({len (stopwords_set)} 个): {stopwords_set} " )
filtered_words = [word for word in words if word not in stopwords_set]
filtered_word_count = len (filtered_words)
print (f"\n过滤后的单词列表 ({filtered_word_count} 个): {filtered_words} " )
removed_count = original_word_count - filtered_word_count
print (f"移除了 {removed_count} 个停用词。" )
return filtered_words
最后,让我们把这些操作综合起来,看一个模拟的社交网络兴趣分析示例。我们可以计算任意两人之间的共同兴趣、独家兴趣,甚至为一个小群体分析兴趣重合度。
def analyze_social_network_interests (people_interests: dict ) -> dict :
""" 分析一组人的兴趣,使用集合运算发现各种关系。
输入是一个字典,键是人名,值是该人兴趣的集合。
此函数展示了集合在关系数据分析中的强大作用。
参数:
people_interests (dict): 格式为 {人名:兴趣集合} 的字典。
返回:
dict: 包含各种分析结果的字典,如共同兴趣、每个人的独家兴趣等。
"""
print ("分析开始..." )
all_interests = set ()
for interests in people_interests.values():
all_interests.update(interests)
common_interests = set (all_interests)
for interests in people_interests.values():
common_interests &= interests
print (f"共同兴趣:{common_interests} " )
print ("分析完成" )
return {"common" : common_interests}
这个函数就运用了并集、交集、差集等多种集合运算,清晰地呈现出数据间的关系。掌握集合,就是掌握了一种处理'唯一性'和'关系比较'问题的利器。
总结 Python 中的集合是一个强大而独特的数据结构。它最核心的特性有两样:无序 和唯一 。想象一下一个装满彩色小球的袋子,你每次伸手进去拿,顺序可能都不同,但袋子里绝不会有两个颜色、大小都完全一样的小球——这就是集合的直观比喻。
无序意味着集合中的元素没有索引位置,你不能像列表那样用 my_set[0] 来访问第一个元素。唯一性则保证了集合中的每个元素都是独一无二的,任何重复的项在创建时就会被自动剔除。
既然不能通过索引,我们如何与集合交互呢?核心操作是添加、移除和检查成员是否存在。比如,管理一个不断变化的标签集合。
集合的真正威力体现在数学运算上,这些操作直观且高效。假设你有两个朋友圈的兴趣爱好集合,想知道他们的共同爱好、或者把所有爱好合并起来看看。
求交集(intersection),找出共同拥有的元素。
求差集(difference),找出'我有但你没有'的元素。
求对称差集(symmetric difference),找出'你我独家,非共同'的元素。
我们还可以分析两个集合之间的关系,比如一个集合是否完全包含另一个,或者它们是否毫无交集。
到这里,我们讨论的都是普通的 set,它是可变的,创建后可以随意增删元素。但 Python 还提供了一个不可变的版本——frozenset(冻结集合)。正如其名,一旦创建,内容就无法更改。这带来了一个关键优势:frozenset 是可哈希的 。
这意味着什么呢?一个对象如果是可哈希的(比如数字、字符串、元组),它就可以作为字典的键(key),或者被放入另一个集合中。而普通的可变集合做不到这一点。
正因为冻结集合可哈希,我们才能实现集合的嵌套,例如创建一个包含多个集合的'大集合'。
了解了核心特性与操作,集合在实际编程中能解决哪些具体问题呢?一个最经典的应用就是快速去重 。相比手动遍历列表,使用集合几乎是一步到位,不过要留意,结果可能会打乱原列表的顺序。
另一个常见场景是多数据源对比 。比如,从几个不同的渠道收集用户邮箱,想找出所有渠道都存在的用户,集合的交集运算能瞬间给出答案。
在文本处理中,集合的高效成员检查 特性(时间复杂度接近 O(1))大放异彩。比如过滤一段文本中的停用词(如'的'、'了'、'和'),用集合来存储停用词会比用列表快得多。
最后,让我们把这些技巧综合起来,模拟一个更复杂的场景:分析社交网络中不同人群的兴趣爱好,计算他们的共同点与独特点。
集合将数学中的集合论思想带入了编程,提供了一种基于元素'存在与否'而非'顺序位置'的思考方式。当你需要处理唯一性项目、进行快速比较或筛选时,它就是最趁手的工具。而冻结集合则弥补了可变集合在需要不可变、可哈希场景下的空缺。