Python-Core-50-Courses/第11课:常用数据结构之集合.md

192 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 第11课常用数据结构之集合
在学习了列表和元组之后我们再来学习一种容器型的数据类型它的名字叫集合set。说到集合这个词大家一定不会陌生在数学课本上就有这个概念。通常我们对集合的定义是“**把一定范围的、确定的、可以区别的事物当作一个整体来看待**”,集合中的各个事物通常称为集合的**元素**。集合应该满足以下特性:
1. **无序性**:一个集合中,每个元素的地位都是相同的,元素之间是无序的。
2. **互异性**:一个集合中,任何两个元素都是不相同的,即元素在集合中只能出现一次。
3. **确定性**:给定一个集合和一个任意元素,该元素要么属这个集合,要么不属于这个集合,二者必居其一,不允许有模棱两可的情况出现。
Python程序中的集合跟数学上的集合是完全一致的需要强调的是上面所说的无序性和互异性。无序性说明集合中的元素并不像列中的元素那样一个挨着一个可以通过索引实现随机访问随机访问指的是给定一个有效的范围随机抽取出一个数字然后通过这个数字可以获取到对应的元素所以Python中的**集合肯定不能够支持索引运算**。另外,集合的互异性决定了**集合中不能有重复元素**这一点也是集合区别于列表的关键说得更直白一些就是Python中的集合类型会对其中的元素做去重处理。Python中的集合一定是支持`in`和`not in`成员运算的,这样就可以确定一个元素是否属于集合,也就是上面所说的集合的确定性。**集合的成员运算在性能上要优于列表的成员运算**,这是集合的底层存储特性(哈希存储)决定的,此处我们暂时不做讨论,大家可以先记下这个结论。
### 创建集合
在Python中创建集合可以使用`{}`字面量语法,`{}`中需要至少有一个元素,因为没有元素的`{}`并不是空集合而是一个空字典,我们下一节课就会大家介绍字典的知识。当然,也可以使用内置函数`set`来创建一个集合,准确的说`set`并不是一个函数,而是创建集合对象的构造器,这个知识点我们很快也会讲到,现在不理解跳过它就可以了。要创建空集合可以使用`set()`;也可以将其他序列转换成集合,例如:`set('hello')`会得到一个包含了4个字符的集合重复的`l`会被去掉)。除了这两种方式,我们还可以使用生成式语法来创建集合,就像我们之前用生成式创建列表那样。要知道集合中有多少个元素,还是使用内置函数`len`;使用`for`循环可以实现对集合元素的遍历。
```Python
# 创建集合的字面量语法(重复元素不会出现在集合中)
set1 = {1, 2, 3, 3, 3, 2}
print(set1) # {1, 2, 3}
print(len(set1)) # 3
# 创建集合的构造器语法(后面会讲到什么是构造器)
set2 = set('hello')
print(set2) # {'h', 'l', 'o', 'e'}
# 将列表转换成集合(可以去掉列表中的重复元素)
set3 = set([1, 2, 3, 3, 2, 1])
print(set3) # {1, 2, 3}
# 创建集合的生成式语法(将列表生成式的[]换成{})
set4 = {num for num in range(1, 20) if num % 3 == 0 or num % 5 == 0}
print(set4) # {3, 5, 6, 9, 10, 12, 15, 18}
# 集合元素的循环遍历
for elem in set4:
print(elem)
```
需要提醒大家,集合中的元素必须是`hashable`类型。所谓`hashable`类型指的是能够计算出哈希码的数据类型大家可以暂时将哈希码理解为和变量对应的唯一的ID值。通常不可变类型都是`hashable`类型,如整数、浮点、字符串、元组等,而可变类型都不是`hashable`类型因为可变类型无法确定唯一的ID值所以也就不能放到集合中。集合本身也是可变类型所以集合不能够作为集合中的元素这一点在使用集合的时候一定要注意。
### 集合的运算
Python为集合类型提供了非常丰富的运算符主要包括成员运算、交集运算、并集运算、差集运算、比较运算相等性、子集、超集等。
#### 成员运算
可以通过成员运算`in`和`not in `检查元素是否在集合中,代码如下所示。
```Python
set1 = {11, 12, 13, 14, 15}
print(10 in set1) # False
print(15 in set1) # True
set2 = {'Python', 'Java', 'Go', 'Swift'}
print('Ruby' in set2) # False
print('Java' in set2) # True
```
#### 交并差运算
Python中的集合跟数学上的集合一样可以进行交集、并集、差集等运算而且可以通过运算符和方法调用两种方式来进行操作代码如下所示。
```Python
set1 = {1, 2, 3, 4, 5, 6, 7}
set2 = {2, 4, 6, 8, 10}
# 交集
# 方法一: 使用 & 运算符
print(set1 & set2) # {2, 4, 6}
# 方法二: 使用intersection方法
print(set1.intersection(set2)) # {2, 4, 6}
# 并集
# 方法一: 使用 | 运算符
print(set1 | set2) # {1, 2, 3, 4, 5, 6, 7, 8, 10}
# 方法二: 使用union方法
print(set1.union(set2)) # {1, 2, 3, 4, 5, 6, 7, 8, 10}
# 差集
# 方法一: 使用 - 运算符
print(set1 - set2) # {1, 3, 5, 7}
# 方法二: 使用difference方法
print(set1.difference(set2)) # {1, 3, 5, 7}
# 对称差
# 方法一: 使用 ^ 运算符
print(set1 ^ set2) # {1, 3, 5, 7, 8, 10}
# 方法二: 使用symmetric_difference方法
print(set1.symmetric_difference(set2)) # {1, 3, 5, 7, 8, 10}
# 方法三: 对称差相当于两个集合的并集减去交集
print((set1 | set2) - (set1 & set2)) # {1, 3, 5, 7, 8, 10}
```
通过上面的代码可以看出,对两个集合求交集,`&`运算符和`intersection`方法的作用是完全相同的,使用运算符的方式更直观而且代码也比较简短。相信大家对交集、并集、差集、对称差这几个概念是比较清楚的,如果没什么印象了可以看看下面的图。
<img src="https://github.com/jackfrued/mypic/raw/master/20210819154520.png" width="90%">
集合的交集、并集、差集运算还可以跟赋值运算一起构成复合赋值运算,如下所示。
```Python
set1 = {1, 3, 5, 7}
set2 = {2, 4, 6}
# 将set1和set2求并集再赋值给set1
# 也可以通过set1.update(set2)来实现
set1 |= set2
print(set1) # {1, 2, 3, 4, 5, 6, 7}
set3 = {3, 6, 9}
# 将set1和set3求交集再赋值给set1
# 也可以通过set1.intersection_update(set3)来实现
set1 &= set3
print(set1) # {3, 6}
```
#### 比较运算
两个集合可以用`==`和`!=`进行相等性判断,如果两个集合中的元素完全相同,那么`==`比较的结果就是`True`,否则就是`False`。如果集合`A`的任意一个元素都是集合`B`的元素,那么集合`A`称为集合`B`的子集,即对于$ \forall{a} \in {A}$,均有$ {a} \in {B} $,则$ {A} \subseteq {B} $`A`是`B`的子集,反过来也可以称`B`是`A`的超集。如果`A`是`B`的子集且`A`不等于`B`,那么`A`就是`B`的真子集。Python为集合类型提供了判断子集和超集的运算符其实就是我们非常熟悉的`<`和`>`运算符,代码如下所示。
```Python
set1 = {1, 3, 5}
set2 = {1, 2, 3, 4, 5}
set3 = set2
# <运算符表示真子集,<=运算符表示子集
print(set1 < set2, set1 <= set2) # True True
print(set2 < set3, set2 <= set3) # False True
# 通过issubset方法也能进行子集判断
print(set1.issubset(set2)) # True
# 反过来可以用issuperset或>运算符进行超集判断
print(set2.issuperset(set1)) # True
print(set2 > set1) # True
```
### 集合的方法
Python中的集合是可变类型我们可以通过集合类型的方法为集合添加或删除元素。
```Python
# 创建一个空集合
set1 = set()
# 通过add方法添加元素
set1.add(33)
set1.add(55)
set1.update({1, 10, 100, 1000})
print(set1) # {33, 1, 100, 55, 1000, 10}
# 通过discard方法删除指定元素
set1.discard(100)
set1.discard(99)
print(set1) # {1, 10, 33, 55, 1000}
# 通过remove方法删除指定元素建议先做成员运算再删除
# 否则元素如果不在集合中就会引发KeyError异常
if 10 in set1:
set1.remove(10)
print(set1) # {33, 1, 55, 1000}
# pop方法可以从集合中随机删除一个元素并返回该元素
print(set1.pop())
# clear方法可以清空整个集合
set1.clear()
print(set1) # set()
```
如果要判断两个集合有没有相同的元素可以使用`isdisjoint`方法,没有相同元素返回`True`,否则返回`False`,代码如下所示。
```Python
set1 = {'Java', 'Python', 'Go', 'Kotlin'}
set2 = {'Kotlin', 'Swift', 'Java', 'Objective-C', 'Dart'}
set3 = {'HTML', 'CSS', 'JavaScript'}
print(set1.isdisjoint(set2)) # False
print(set1.isdisjoint(set3)) # True
```
### 不可变集合
Python中还有一种不可变类型的集合名字叫`frozenset`。`set`跟`frozenset`的区别就如同`list`跟`tuple`的区别,`frozenset`由于是不可变类型,能够计算出哈希码,因此它可以作为`set`中的元素。除了不能添加和删除元素,`frozenset`在其他方面跟`set`基本是一样的,下面的代码简单的展示了`frozenset`的用法。
```Python
set1 = frozenset({1, 3, 5, 7})
set2 = frozenset(range(1, 6))
print(set1 & set2) # frozenset({1, 3, 5})
print(set1 | set2) # frozenset({1, 2, 3, 4, 5, 7})
print(set1 - set2) # frozenset({7})
print(set1 < set2) # False
```
### 简单的总结
Python中的集合底层使用了**哈希存储**的方式,对于这一点我们暂时不做介绍,在后面的课程有需要的时候再为大家讲解集合的底层原理,现阶段大家只需要知道**集合是一种容器**,元素必须是`hashable`类型,与列表不同的地方在于集合中的元素**没有序**、**不能用索引运算**、**不能重复**。