Python学习笔记-集合
🐍

Python学习笔记-集合

Tags
Computer Science
Tech
Published
March 1, 2024
Author
Shuang Tian
在Python中,集合(set)是一个无序的,不包含重复元素的数据类型。集合的主要用途是进行成员关系测试和消除重复项。集合对象还支持数学运算,如并集、交集、差集和对称差分。

创建集合

集合可以通过大括号 {}set() 函数创建。例如:
# 创建集合 s = {1, 2, 3, 4, 5} print(s) # 输出: {1, 2, 3, 4, 5} # 使用set()函数创建集合 s = set([1, 2, 3, 4, 5]) print(s) # 输出: {1, 2, 3, 4, 5}
set() 构造器可以接收任何可迭代对象(如列表、元组、字典等)作为参数,然后将其转换为一个集合。在转换过程中,所有重复的元素都会被移除。以下是一些使用 set() 构造器创建集合的例子:
# 从列表创建集合 list1 = [1, 2, 3, 4, 5, 5, 5, 6, 6] set_from_list = set(list1) print(set_from_list) # 输出: {1, 2, 3, 4, 5, 6} # 从元组创建集合 tuple1 = (1, 2, 3, 4, 5, 5, 5, 6, 6) set_from_tuple = set(tuple1) print(set_from_tuple) # 输出: {1, 2, 3, 4, 5, 6} # 从字符串创建集合 str1 = "Hello, World!" set_from_str = set(str1) print(set_from_str) # 输出: {'!', ' ', 'o', 'r', 'H', 'd', ',', 'W', 'l', 'e'}
注意:在这些例子中,输出的集合元素的顺序可能与输入的顺序不同,因为集合是无序的。此外,所有的重复元素都被移除了。
如果想创建一个空集合,可以调用 set() 构造器而不传入任何参数:
# 创建空集合 empty_set = set() print(empty_set) # 输出: set()
注意:不能使用 {} 来创建一个空集合,因为 {} 在Python中表示一个空字典。
在Python中,可以使用集合推导式(set comprehension)来创建集合,这与列表推导式非常相似。集合推导式是一个简洁的方法,用于通过一个现有的可迭代对象创建新的集合。
集合推导式的基本语法是:
new_set = {expression for item in iterable}
其中,expression 是根据 item 计算出的新元素,iterable 是任何可以迭代的对象(如列表、元组、字典、集合等)。
以下是一些使用集合推导式的例子:
# 从一个列表创建一个新的集合,其中包含列表中每个元素的平方 list1 = [1, 2, 3, 4, 5] squared_set = {x**2 for x in list1} print(squared_set) # 输出: {1, 4, 9, 16, 25} # 从一个字符串创建一个新的集合,其中包含字符串中每个字符的大写形式 str1 = "Hello, World!" uppercase_set = {char.upper() for char in str1} print(uppercase_set) # 输出: {'!', ' ', 'O', 'R', 'H', 'D', ',', 'W', 'L', 'E'}
你也可以在集合推导式中使用 if 条件语句来过滤元素:
# 从一个列表创建一个新的集合,其中只包含列表中的偶数 list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] even_set = {x for x in list1 if x % 2 == 0} print(even_set) # 输出: {8, 2, 4, 6}

集合元素的访问

由于集合是无序的,我们不能像列表或元组那样通过索引来访问或改变集合中的元素。然而,我们可以使用循环语句来遍历集合中的所有元素,或者使用 in 关键字来检查一个元素是否存在于集合中。
以下是一些例子:
# 创建一个集合 fruits = {"apple", "banana", "cherry"} # 使用 for 循环遍历集合 for fruit in fruits: print(fruit) # 使用 in 关键字检查元素是否存在于集合中 if "banana" in fruits: print("Banana is in the set") else: print("Banana is not in the set")
在这个例子中,for 循环被用来遍历集合中的每一个元素,而 in 关键字被用来检查 "banana" 是否存在于集合中。
请注意,尽管我们不能改变集合中已有的元素,但我们可以添加新的元素或者删除已有的元素。我们可以使用 add() 方法来添加一个元素,使用 remove()discard() 方法来删除一个元素。详细见下方笔记。

集合运算

集合支持多种运算,包括并集、交集、差集和对称差集。这些运算可以使用方法或者运算符来完成。
  1. 并集(Union)A.union(B)A | B:并集操作会返回包含两个集合所有元素的新集合。重复的元素只会出现一次。可以使用 union() 方法或者 | 运算符来完成。
    1. set1 = {1, 2, 3} set2 = {3, 4, 5} print(set1.union(set2)) # 输出: {1, 2, 3, 4, 5} print(set1 | set2) # 输出: {1, 2, 3, 4, 5}
  1. 交集(Intersection)A.intersection(B)A & B:交集操作会返回两个集合共有的元素。可以使用 intersection() 方法或者 & 运算符来完成。
    1. set1 = {1, 2, 3} set2 = {3, 4, 5} print(set1.intersection(set2)) # 输出: {3} print(set1 & set2) # 输出: {3}
  1. 差集(Difference)A.difference(B)A - B:差集操作会返回只在第一个集合中存在的元素。可以使用 difference() 方法或者-运算符来完成。
    1. set1 = {1, 2, 3} set2 = {3, 4, 5} print(set1.difference(set2)) # 输出: {1, 2} print(set1 - set2) # 输出: {1, 2}
  1. 对称差集(Symmetric Difference)A.symmetric_difference(B)A ^ B:对称差集操作会返回只在其中一个集合中存在的元素。可以使用 symmetric_difference() 方法或者 ^ 运算符来完成。
    1. set1 = {1, 2, 3} set2 = {3, 4, 5} print(set1.symmetric_difference(set2)) # 输出: {1, 2, 4, 5} print(set1 ^ set2) # 输出: {1, 2, 4, 5}

集合间关系的判断

Python 提供了多种方法和运算符来判断两个集合之间的关系,包括是否是子集、超集或者不相交。
  1. issubset():如果一个集合是另一个集合的子集,则 issubset() 方法返回 True,否则返回 False
    1. set1 = {1, 2, 3} set2 = {1, 2, 3, 4, 5} print(set1.issubset(set2)) # 输出: True
  1. issuperset():如果一个集合是另一个集合的超集,则 issuperset() 方法返回 True,否则返回 False
    1. set1 = {1, 2, 3, 4, 5} set2 = {1, 2, 3} print(set1.issuperset(set2)) # 输出: True
  1. isdisjoint():如果两个集合没有任何共同的元素(即它们的交集为空集),则 isdisjoint() 方法返回 True,否则返回 False
    1. set1 = {1, 2, 3} set2 = {4, 5, 6} print(set1.isdisjoint(set2)) # 输出: True
此外,Python 还提供了 <<=>>= 运算符来进行子集和超集的检查:
set1 = {1, 2, 3} set2 = {1, 2, 3, 4, 5} print(set1 < set2) # 输出: True,检查 set1 是否是 set2 的真子集 print(set1 <= set2) # 输出: True,检查 set1 是否是 set2 的子集 print(set2 > set1) # 输出: True,检查 set2 是否是 set1 的真超集 print(set2 >= set1) # 输出: True,检查 set2 是否是 set1 的超集
请注意,<> 检查的是真子集和真超集,也就是说,如果两个集合相等,<> 会返回 False。而 <=>= 则会在集合相等时返回 True

集合方法

Python 提供了多种方法来修改集合,包括添加元素、删除元素以及更新集合。
  1. add():添加一个元素到集合中。如果元素已经存在于集合中,那么该操作不会有任何效果。
    1. set1 = {1, 2, 3} set1.add(4) # set1 现在是 {1, 2, 3, 4}
  1. remove():从集合中删除一个元素。如果元素不存在于集合中,那么会抛出一个 KeyError 异常。
    1. set1 = {1, 2, 3} set1.remove(2) # set1 现在是 {1, 3}
  1. discard():从集合中删除一个元素。如果元素不存在于集合中,那么该操作不会有任何效果。
    1. set1 = {1, 2, 3} set1.discard(2) # set1 现在是 {1, 3}
  1. pop():删除并返回集合中的一个元素。由于集合是无序的,所以无法预知哪个元素会被删除。如果集合为空,那么会抛出一个 KeyError 异常。
    1. set1 = {1, 2, 3} elem = set1.pop() # elem 是 1, 2 或 3,set1 现在少了一个元素
  1. clear():删除集合中的所有元素。
    1. set1 = {1, 2, 3} set1.clear() # set1 现在是空集合 {}
  1. update():使用一个或多个其他集合来更新当前集合。这个操作等同于对这些集合进行并集操作,然后赋值给当前集合。
    1. set1 = {1, 2, 3} set2 = {3, 4, 5} set1.update(set2) # set1 现在是 {1, 2, 3, 4, 5}
      Python 的集合对象提供了一些方法,可以直接在原始集合上进行操作,而不需要创建新的集合。这些方法包括 intersection_update(), difference_update()symmetric_difference_update()
    2. intersection_update():这个方法获取两个集合的交集,并将结果更新到原始集合。例如:
      1. set1 = {1, 2, 3, 4} set2 = {3, 4, 5, 6} set1.intersection_update(set2) # set1 现在是 {3, 4}
        在这个例子中,set1 被更新为 set1set2 的交集 {3, 4}
    3. difference_update():这个方法获取两个集合的差集,并将结果更新到原始集合。例如:
      1. set1 = {1, 2, 3, 4} set2 = {3, 4, 5, 6} set1.difference_update(set2) # set1 现在是 {1, 2}
        在这个例子中,set1 被更新为 set1set2 的差集 {1, 2}
    4. symmetric_difference_update():这个方法获取两个集合的对称差集,并将结果更新到原始集合。对称差集是只在其中一个集合中出现的元素。例如:
      1. set1 = {1, 2, 3, 4} set2 = {3, 4, 5, 6} set1.symmetric_difference_update(set2) # set1 现在是 {1, 2, 5, 6}
        在这个例子中,set1 被更新为 set1set2 的对称差集 {1, 2, 5, 6}
        这些方法都会直接修改原始集合,而不会返回任何值。如果不想修改原始集合,可以使用 intersection(), difference()symmetric_difference() 方法,它们会返回一个新的集合,而不会修改原始集合。
以上就是 Python 提供的一些集合修改操作。需要注意的是,由于集合中的元素必须是不可变的,所以不能添加像列表或字典这样的可变元素到集合中。
集合的一个重要特性是集合中的元素必须是不可变的,这意味着我们可以添加数、字符串和元组到集合中,但不能添加列表或字典,因为它们是可变的。
集合在进行成员关系测试和消除重复项时非常有用。例如,检查一个列表中是否存在重复项的一种简单方法是将列表转换为集合,然后比较集合和列表的长度。如果集合的长度小于列表的长度,那么列表中就存在重复项。

不可变集合

在 Python 中,集合可以是可变的(mutable)或不可变的(immutable)。我们已经讨论了可变集合,它们可以添加和删除元素。然而,Python 也提供了一种叫做 "frozenset" 的不可变集合。
frozenset 是一种特殊类型的集合,它是不可变的,也就是说,一旦创建了一个 frozenset,就不能再向其中添加或删除元素。这使得 frozenset 可以用作字典的键或其他集合的元素,这是普通集合不能做到的。
创建 frozenset 的语法如下:
fs = frozenset([1, 2, 3, 4, 5])
在这个例子中,我们创建了一个包含五个元素的 frozenset。
虽然不能修改 frozenset 的内容,但是可以对 frozenset 进行集合操作,如并集、交集、差集和对称差集。例如:
fs1 = frozenset([1, 2, 3, 4, 5]) fs2 = frozenset([4, 5, 6, 7, 8]) fs3 = fs1.union(fs2) # fs3 是 frozenset([1, 2, 3, 4, 5, 6, 7, 8]) fs4 = fs1.intersection(fs2) # fs4 是 frozenset([4, 5])
总的来说,frozenset 提供了一种创建不可变集合的方式,这在需要集合的性质(如元素的唯一性和集合操作),但又需要不可变性(如用作字典的键)的情况下非常有用。
在一个集合中可以嵌套一个冻结集合。例如:
# Create a frozenset fs = frozenset([1, 2, 3]) # Create a set with the frozenset as an element s = {fs, 4, 5} print(s) # Prints: {frozenset({1, 2, 3}), 4, 5}
在这个例子中,我们首先创建了一个冻结集合 fs,然后我们创建了一个集合 s,并将 fs 作为一个元素添加到 s 中。这是允许的,因为冻结集合是不可变的。

哈希值

在 Python 中,哈希值是一个固定大小的整数,由哈希函数计算得出。哈希函数可以将不同大小和类型的数据转换为固定大小的整数。在 Python 中,hash() 函数用于获取对象的哈希值。
哈希值的主要用途是在哈希表中快速查找数据,这是 Python 字典和集合的基础。哈希表是一种数据结构,它使用哈希值将键映射到值,从而实现快速查找。
在 Python 中,只有不可变数据类型(如整数、浮点数、字符串、元组等)可以被哈希,因为它们的值在创建后不能更改。如果对象的值可以改变(如列表或字典),那么在对象的生命周期中,其哈希值也可能会改变,这会破坏哈希表。
这就是为什么 Python 的字典和集合只能使用不可变类型作为键的原因。如果你尝试使用可变类型(如列表)作为字典的键,Python 会抛出一个类型错误。
这是一个获取 Python 对象哈希值的示例:
print(hash(123)) # 整数 print(hash(12.34)) # 浮点数 print(hash('Hello')) # 字符串 print(hash((1, 2, 'Three'))) # 元组
需要注意的是,哈希值是在 Python 进程的生命周期内保持一致的,但在不同的 Python 进程或不同的 Python 运行时,相同对象的哈希值可能会不同。
Python 中整数的哈希值就是其本身。例如,hash(1) 的结果是 1hash(123) 的结果是 123,等等。 这是 Python 的设计决定,使得对整数的哈希操作非常快速和简单。
这里是一个例子:
print(hash(0)) # 输出: 0 print(hash(1)) # 输出: 1 print(hash(123)) # 输出: 123 print(hash(-1)) # 输出: -2 hash(-1) 在 Python 中的结果是 -2,而不是 -1。这是 Python 的一个特殊情况。在 Python 中,-1 是一个特殊的哈希值,它被用来表示错误。因此,为了避免冲突,hash(-1) 的结果被特别设定为 -2。这是 Python 的设计决策,为了在内部处理哈希错误。 print(hash(-123)) # 输出: -123
这种设计使得哈希操作对于整数来说非常高效。然而,需要注意的是,这种行为并不适用于所有类型的数据。例如,浮点数和字符串的哈希值就不是它们本身。
在 Python 中,可变对象通常是不可哈希的,而不可变对象通常是可哈希的。哈希值是一个固定大小的整数,它是由哈希函数从对象的内容计算出来的。如果一个对象是可哈希的,那么在该对象的生命周期内,它的哈希值是不变的。这就是为什么不可变对象(如整数、浮点数、字符串、元组)是可哈希的:一旦创建,它们的内容就不能改变。另一方面,可变对象(如列表、字典、集合)的内容可以改变,因此它们是不可哈希的。如果它们是可哈希的,那么当它们的内容改变时,它们的哈希值也会改变,这将破坏哈希表的性质,使得查找、插入和删除操作的效率大大降低。因此,Python 设计了这样的规则:只有不可变对象才能被哈希,而可变对象不能被哈希。这也是为什么在 Python 中,只有不可变对象可以作为字典的键,因为字典的键需要是可哈希的。
在某些特殊情况下,可变对象也可以设计为可哈希的。一个例子是如果创建了一个自定义的类,并且该类的实例在创建之后其内部状态不会改变,即使它是技术上的可变对象,也可以选择使其可哈希。可以通过在类中定义一个 __hash__() 方法来实现这一点。这个 __hash__() 方法应该返回一个整数,这个整数将作为该对象的哈希值。然后,就可以使用该类的实例作为字典的键。例如:
class MyHashableClass: def __init__(self, value): self.value = value def __hash__(self): return hash(self.value) my_instance = MyHashableClass(10) my_dict = {my_instance: 'Hello'} print(my_dict[my_instance]) # Prints: Hello
在这个例子中,MyHashableClass 是一个可变对象,因为它的 value 属性可以在创建之后改变。但是,由于我们定义了一个 __hash__() 方法,所以它的实例是可哈希的,并且可以作为字典的键。
然而,需要注意的是,如果选择使一个可变对象可哈希,必须确保它的哈希值在其生命周期内不会改变。否则,如果哈希值改变,那么使用这个对象作为字典的键将会导致问题。