MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python集合的操作与应用

2021-11-211.5k 阅读

Python集合的基本概念

在Python中,集合(set)是一种无序且不包含重复元素的数据结构。集合以花括号 {}set() 函数来创建。例如:

my_set = {1, 2, 3}
print(my_set)

运行上述代码,会输出 {1, 2, 3}。这里要注意,如果尝试创建一个包含重复元素的集合,Python会自动去除重复项。例如:

duplicate_set = {1, 2, 2, 3}
print(duplicate_set)

输出结果为 {1, 2, 3},其中重复的 2 被去除了。

空集合不能直接用 {} 创建,因为这会被Python解释为一个空字典。要创建空集合,必须使用 set() 函数,如下所示:

empty_set = set()
print(type(empty_set))

上述代码会输出 <class'set'>,表明 empty_set 确实是一个集合。

集合的元素类型

集合中的元素必须是不可变类型,如整数、浮点数、字符串、元组等。列表、字典和其他集合本身是可变的,不能作为集合的元素。例如,下面的代码会引发错误:

try:
    bad_set = {[1, 2], 3}
except TypeError as e:
    print(e)

运行上述代码,会得到类似 unhashable type: 'list' 的错误信息,这是因为列表是不可哈希的(不可哈希意味着对象在其生命周期内不可变,以便可以作为集合元素或字典键),而集合要求元素是可哈希的。

集合的操作

成员测试

使用 innot in 关键字可以测试一个元素是否在集合中。例如:

my_set = {1, 2, 3}
print(2 in my_set)
print(4 not in my_set)

上述代码会分别输出 TrueTrue,表示 2 在集合 my_set 中,而 4 不在集合 my_set 中。

添加元素

可以使用 add() 方法向集合中添加单个元素。例如:

my_set = {1, 2, 3}
my_set.add(4)
print(my_set)

运行上述代码,会输出 {1, 2, 3, 4},表明元素 4 已成功添加到集合中。

如果尝试添加已经存在于集合中的元素,集合不会发生变化,因为集合不允许重复元素。例如:

my_set = {1, 2, 3}
my_set.add(3)
print(my_set)

输出仍然是 {1, 2, 3}

更新集合

update() 方法用于将一个可迭代对象(如列表、元组、另一个集合等)中的所有元素添加到集合中。例如:

my_set = {1, 2, 3}
new_elements = [4, 5]
my_set.update(new_elements)
print(my_set)

上述代码会输出 {1, 2, 3, 4, 5},列表 new_elements 中的元素被添加到了集合 my_set 中。

update() 方法也可以接受多个可迭代对象作为参数。例如:

my_set = {1, 2, 3}
list1 = [4, 5]
tuple1 = (6, 7)
my_set.update(list1, tuple1)
print(my_set)

输出为 {1, 2, 3, 4, 5, 6, 7},列表 list1 和元组 tuple1 中的元素都被添加到了集合 my_set 中。

删除元素

  1. remove() 方法remove() 方法用于移除集合中的指定元素。如果元素不存在,会引发 KeyError 异常。例如:
my_set = {1, 2, 3}
my_set.remove(2)
print(my_set)

运行上述代码,会输出 {1, 3},元素 2 被成功移除。

如果尝试移除不存在的元素,如:

my_set = {1, 2, 3}
try:
    my_set.remove(4)
except KeyError as e:
    print(e)

会得到类似 4 的错误信息,表明元素 4 不存在于集合中。

  1. discard() 方法discard() 方法也用于移除集合中的指定元素,但如果元素不存在,不会引发异常。例如:
my_set = {1, 2, 3}
my_set.discard(2)
print(my_set)

输出为 {1, 3},与 remove() 方法类似,但:

my_set = {1, 2, 3}
my_set.discard(4)
print(my_set)

输出仍然是 {1, 2, 3},因为元素 4 不存在,discard() 方法不会引发异常。

  1. pop() 方法pop() 方法用于随机移除并返回集合中的一个元素。由于集合是无序的,无法确定具体移除哪个元素。例如:
my_set = {1, 2, 3}
removed_element = my_set.pop()
print(removed_element)
print(my_set)

可能会输出类似 1{2, 3} 的结果,表明随机移除了元素 1 并返回。

如果集合为空,调用 pop() 方法会引发 KeyError 异常。例如:

empty_set = set()
try:
    empty_set.pop()
except KeyError as e:
    print(e)

会得到类似 pop from an empty set 的错误信息。

  1. clear() 方法clear() 方法用于移除集合中的所有元素,将集合变为空集合。例如:
my_set = {1, 2, 3}
my_set.clear()
print(my_set)

输出为 set(),表明集合已被清空。

集合的数学运算

并集

并集操作返回包含两个集合中所有不重复元素的新集合。在Python中,可以使用 | 运算符或 union() 方法。例如:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
union_set = set1 | set2
print(union_set)

输出为 {1, 2, 3, 4, 5},通过 | 运算符得到了两个集合的并集。

使用 union() 方法也能得到相同结果:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
union_set = set1.union(set2)
print(union_set)

同样输出 {1, 2, 3, 4, 5}

交集

交集操作返回两个集合中共同拥有的元素组成的新集合。可以使用 & 运算符或 intersection() 方法。例如:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
intersection_set = set1 & set2
print(intersection_set)

输出为 {3},这是两个集合的交集。

使用 intersection() 方法:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
intersection_set = set1.intersection(set2)
print(intersection_set)

同样输出 {3}

差集

差集操作返回在第一个集合中但不在第二个集合中的元素组成的新集合。可以使用 - 运算符或 difference() 方法。例如:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
difference_set = set1 - set2
print(difference_set)

输出为 {1, 2},这是 set1 相对于 set2 的差集。

使用 difference() 方法:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
difference_set = set1.difference(set2)
print(difference_set)

同样输出 {1, 2}

对称差集

对称差集返回在两个集合中但不同时在两个集合中的元素组成的新集合。可以使用 ^ 运算符或 symmetric_difference() 方法。例如:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
symmetric_difference_set = set1 ^ set2
print(symmetric_difference_set)

输出为 {1, 2, 4, 5},这是两个集合的对称差集。

使用 symmetric_difference() 方法:

set1 = {1, 2, 3}
set2 = {3, 4, 5}
symmetric_difference_set = set1.symmetric_difference(set2)
print(symmetric_difference_set)

同样输出 {1, 2, 4, 5}

集合的比较

子集

如果集合 A 的所有元素都在集合 B 中,则称集合 A 是集合 B 的子集。可以使用 <= 运算符或 issubset() 方法来判断。例如:

set1 = {1, 2}
set2 = {1, 2, 3}
print(set1 <= set2)
print(set1.issubset(set2))

上述代码会分别输出 TrueTrue,表明 set1set2 的子集。

如果集合 A 是集合 B 的子集且 A 不等于 B,则称 AB 的真子集,可以使用 < 运算符。例如:

set1 = {1, 2}
set2 = {1, 2, 3}
print(set1 < set2)

输出为 True,表明 set1set2 的真子集。

超集

如果集合 B 的所有元素都在集合 A 中,则称集合 A 是集合 B 的超集。可以使用 >= 运算符或 issuperset() 方法来判断。例如:

set1 = {1, 2, 3}
set2 = {1, 2}
print(set1 >= set2)
print(set1.issuperset(set2))

上述代码会分别输出 TrueTrue,表明 set1set2 的超集。

如果集合 A 是集合 B 的超集且 A 不等于 B,则称 AB 的真超集,可以使用 > 运算符。例如:

set1 = {1, 2, 3}
set2 = {1, 2}
print(set1 > set2)

输出为 True,表明 set1set2 的真超集。

集合的应用场景

数据去重

在处理大量数据时,经常会遇到数据重复的问题。集合的特性使其成为数据去重的理想工具。例如,假设我们有一个包含重复元素的列表,要去除重复元素,可以将其转换为集合,然后再转换回列表。例如:

duplicate_list = [1, 2, 2, 3, 3, 3]
unique_set = set(duplicate_list)
unique_list = list(unique_set)
print(unique_list)

输出为 [1, 2, 3],成功去除了列表中的重复元素。

数学计算

在数学计算中,集合的各种运算(如并集、交集、差集等)非常有用。例如,在处理概率问题时,可能需要计算事件集合的交集和并集。假设我们有两个事件集合,要计算它们同时发生的概率(即交集的概率),可以使用集合的交集运算。例如:

event1 = {1, 2, 3}
event2 = {3, 4, 5}
intersection_event = event1 & event2
print(intersection_event)

得到的交集 {3} 可以用于进一步的概率计算。

关系判断

在实际应用中,经常需要判断两个数据集之间的关系,如子集、超集关系等。例如,在权限管理系统中,可能有不同用户组的权限集合,需要判断某个用户组的权限是否是另一个用户组权限的子集,以确定权限继承关系。例如:

admin_permissions = {'read', 'write', 'delete'}
user_permissions = {'read'}
print(user_permissions <= admin_permissions)

输出为 True,表明用户权限是管理员权限的子集。

集合的遍历

虽然集合是无序的,但仍然可以使用 for 循环来遍历集合中的元素。例如:

my_set = {1, 2, 3}
for element in my_set:
    print(element)

上述代码会依次输出集合中的元素 123,但顺序不一定是 123,因为集合是无序的。

集合推导式

类似于列表推导式,Python也支持集合推导式,用于快速创建集合。集合推导式的语法为 {expression for item in iterable if condition}。例如,要创建一个包含 15 的平方的集合,可以使用以下集合推导式:

square_set = {num ** 2 for num in range(1, 6)}
print(square_set)

输出为 {1, 4, 9, 16, 25},通过集合推导式快速创建了一个包含 15 的平方的集合。

如果添加条件,例如只包含偶数的平方,可以这样写:

even_square_set = {num ** 2 for num in range(1, 6) if num % 2 == 0}
print(even_square_set)

输出为 {4, 16},只包含了 24 的平方。

冻结集合

Python还提供了一种特殊的集合类型——冻结集合(frozenset)。冻结集合是不可变的集合,一旦创建就不能再修改。它使用 frozenset() 函数来创建。例如:

my_frozenset = frozenset({1, 2, 3})
print(my_frozenset)

输出为 frozenset({1, 2, 3})

由于冻结集合是不可变的,它可以作为字典的键或其他集合的元素。例如:

my_dict = {frozenset({1, 2}): 'value'}
print(my_dict)

上述代码创建了一个以冻结集合为键的字典。

冻结集合也支持集合的一些数学运算,如并集、交集等,但返回的结果仍然是冻结集合。例如:

fs1 = frozenset({1, 2})
fs2 = frozenset({2, 3})
union_fs = fs1 | fs2
print(union_fs)

输出为 frozenset({1, 2, 3}),得到了两个冻结集合的并集。

冻结集合在需要不可变集合的场景中非常有用,例如在缓存机制中,如果需要将集合作为缓存键,就可以使用冻结集合。

集合的性能特点

集合在成员测试(innot in 操作)方面具有非常高的性能。由于集合内部使用哈希表来存储元素,平均情况下,成员测试的时间复杂度为 $O(1)$,这意味着无论集合中有多少元素,测试一个元素是否在集合中的时间几乎是恒定的。

在添加和删除元素方面,add()discard()remove() 方法的平均时间复杂度也为 $O(1)$。但 pop() 方法的时间复杂度在最坏情况下为 $O(n)$,因为它需要随机移除一个元素,可能需要遍历整个集合。

在进行集合的数学运算(如并集、交集、差集等)时,时间复杂度与参与运算的集合大小有关。例如,并集运算 |union() 方法的时间复杂度为 $O(len(A) + len(B))$,其中 AB 是两个集合;交集运算 &intersection() 方法的时间复杂度为 $O(min(len(A), len(B)))$。

集合与其他数据结构的比较

  1. 与列表的比较

    • 有序性:列表是有序的,元素按照插入顺序存储,可以通过索引访问元素;而集合是无序的,不能通过索引访问元素。
    • 重复元素:列表允许重复元素,而集合自动去除重复元素。
    • 性能:在成员测试方面,集合的性能比列表高很多。例如,在一个包含大量元素的列表和集合中测试一个元素是否存在,集合的 in 操作平均时间复杂度为 $O(1)$,而列表的 in 操作时间复杂度为 $O(n)$,其中 n 是列表的长度。
    • 内存占用:由于集合需要使用哈希表来存储元素,在存储相同数量元素的情况下,集合通常比列表占用更多的内存。
  2. 与字典的比较

    • 结构特点:字典是键值对的集合,每个键必须是唯一的,而集合只包含元素,没有键值对的概念。
    • 元素可变性:字典的值可以是可变或不可变类型,但键必须是不可变类型;集合的元素必须是不可变类型。
    • 应用场景:字典主要用于根据键快速查找值,而集合更侧重于数据去重、数学运算和关系判断等场景。

集合在实际项目中的应用案例

  1. Web开发中的数据验证 在Web开发中,经常需要验证用户输入的数据是否唯一。例如,在用户注册功能中,需要确保用户名的唯一性。可以将已注册的用户名存储在一个集合中,当有新用户注册时,通过集合的成员测试来快速判断用户名是否已存在。例如:
registered_usernames = {'user1', 'user2'}
new_username = 'user3'
if new_username not in registered_usernames:
    # 执行注册逻辑
    registered_usernames.add(new_username)
    print(f'{new_username} 注册成功')
else:
    print(f'{new_username} 已存在,请重新选择')
  1. 数据挖掘中的频繁项集挖掘 在数据挖掘领域,频繁项集挖掘是一个重要的任务。例如,在购物篮分析中,需要找出经常一起购买的商品组合。可以将每个购物篮中的商品看作一个集合,通过集合的交集运算来找出频繁出现的商品组合。假设有以下购物篮数据:
basket1 = {'apple', 'banana', 'orange'}
basket2 = {'banana', 'grape'}
basket3 = {'apple', 'banana'}

可以通过计算交集来找出频繁一起出现的商品,如:

common_items = basket1 & basket2 & basket3
print(common_items)

如果 common_items 中有元素,说明这些元素是在多个购物篮中频繁出现的商品。

  1. 网络爬虫中的URL去重 在网络爬虫中,为了避免重复爬取相同的URL,需要对已访问的URL进行去重。可以使用集合来存储已访问的URL,每次访问一个新URL时,先检查该URL是否在集合中。例如:
visited_urls = set()
new_url = 'http://example.com'
if new_url not in visited_urls:
    # 爬取该URL
    visited_urls.add(new_url)
    print(f'正在爬取 {new_url}')
else:
    print(f'{new_url} 已访问过,跳过')

通过这种方式,可以有效地避免重复爬取,提高爬虫的效率。

通过以上对Python集合的深入探讨,我们了解了集合的基本概念、操作、应用场景以及与其他数据结构的比较等内容。在实际编程中,根据具体需求合理使用集合,可以提高程序的效率和代码的简洁性。无论是数据处理、数学计算还是关系判断等任务,集合都能发挥重要作用。同时,了解集合的性能特点和与其他数据结构的差异,有助于我们在不同场景下做出更合适的选择。