12. 数据类型 - 集合详解

閱讀時間約 28 分鐘
raw-image

Hi, 大家好。我是茶桁

通过最近几节课的内容,我们已经了解到了大部分的容器类数据的特性和应用,今天这一节课是容器类数据的最后一部分。让我们今天来详细了解一下「集合」。

集合是确定的一组无序的数据的组合。注意这一句话中的几个概念:

  • 首先是「确定的」,当前集合中的元素的值是不能重复的。
  • 集合是由多个数据组合的容器类型数据
  • 集合中的数据没有先后顺序
  • 集合的作用大多数时候是为了从成员检测、从无序列中去除重复项。还有就是数学中的集合类计算,例如交集、并集、差集一集对称差集等等。

集合的定义

集合的定义和字典类数据的定义非常像,包含了三种定义方式:

  • 可以直接使用{}来定义集合
  • 可以使用set()进行集合的定义和转换
  • 使用集合推导式来完成集合的定义

⚠️ 需要注意:集合中的元素不能重复,集合中存放的数据为:Number, String, Tuple,冰冻集合

冰冻集合

在集合的定义部分,其他数据类型我们都能理解,唯独多出来一个冰冻集合似乎没有见过,也难以理解。

冰冻集合的定义,需要且仅能使用frozenset()函数来进行定义。故名思义,冰冻集合一旦定义之后,是不能进行修改的,只能做一些集合相关的运算,比如交集,差集等等。

回过头来看冰冻集合的定义函数frozenset(), 这个函数本身是一个强制转换类的函数,可以把其他任何容器类型的数据转为冰冻集合,然后参与集合运算。

# 定义一个冰冻集合
mySets = frozenset({'love', 666, 333, 2, 'a', 1, 2, 'MAMT','55IW'})

# 遍历集合
for i in mySets:
print(i, end=", ")

---
1, 2, 55IW, love, 333, MAMT, 666, a,

也是可以看到,打印的结果完全没有任何顺序。

冰冻集合当然也可以使用集合推导式:

res = frozenset({i<<1 for i in range(6)})
print(res)

---
frozenset({0, 2, 4, 6, 8, 10})

可以进行拷贝:

res = res.copy()
print(res)

---
frozenset({0, 2, 4, 6, 8, 10})

当然冰冻集合也可以进行集合的运算,不过这部分我们将在后面讲解集合的时候来学习。暂时我们只是对「冰冻集合」的概念有个了解就可以了。

集合的基本操作和常规函数

以往的几节,我们都是将集合的操作和函数分开来讲,而这次我们放在一起讲。其实也没其他原因,就是因为这部分的内容并没有多少,并且很容易理解。

# 定义集合
mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1}
mySets

---
{(1, 2, 3), 123, '123', 3.1415, False, True, 'abc', 'love'}

打印的结果再一次验证了集合无序,除此之外,我们可以看到打印出来的集合比我们进行定义的时候似乎少了,这又是为什么呢?

原来,在集合内布尔类型的数据其实就是0和1, True表示为1, False表示为0,而集合内的值是不能重复的,所以,布尔值和0,1就只能存在一个。

我们来尝试检测一下集合中的值,和其他容器类数据一样,我们可以直接使用for...in

# 检测集合中的值
print('123' in mySets)
print('123' not in mySets)

---
True
False

然后一样,可以使用len()检测长度:

print(len(mySets))

---
8

遍历的方法依然是用for

for i in mySets:
print(i, type(i))

---
False <class 'bool'>
True <class 'bool'>
3.1415 <class 'float'>
(1, 2, 3) <class 'tuple'>
love <class 'str'>
abc <class 'str'>
123 <class 'int'>
123 <class 'str'>

为什么我这次会将数据类型也打印出来呢?是因为我想让大家好好记住这些类型,目前集合就只支持这些数据类型,其他的并不支持放入。比如说列表,是无法进入集合内的:

# 看看列表是否能放入
mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1,['list', 2, 3, 4, 5]}

---
TypeError: unhashable type: 'list'

可以看到报错信息提示,不支持类型:列表。

那我们如何像集合中追加元素呢?可以使用add()

# 定义集合
mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1}
res = mySets.add('茶桁')
print(mySets, '\nres:', res)

---
{False, True, 3.1415, (1, 2, 3), 'love', '茶桁', 'abc', 123, '123'}
res: None

可以看到我们在其中追加了一个字符串茶桁, 但是我们再一次验证了集合的无序,新加入的字符串并没有和其他数据类型一样新加入的元素放在最末端。

并且我们注意到了,我用res来接收了add()的返回值,返回了一个None

除了追加之外,当然我们也可以对集合进行删除元素的处理:

res = mySets.pop()
print(mySets, '\nres:', res)

---
{True, 3.1415, (1, 2, 3), 'love', '茶桁', 'abc', 123, '123'}
res: False

pop()删除集合内的元素是随机的,并且,会将删除的元素返回。

如果想指定删除集合中的元素有没有办法呢?其实也有,remove()discard()都可以做到,但是两者又有些区别,我们接着看代码:

# 使用remove()
res = mySets.remove('abc')
print(mySets)
res = mySets.remove('aaa')

---
{True, 3.1415, (1, 2, 3), 'love', '茶桁', 123, '123'}
res: None
KeyError: 'aaa'

能看到,remove()确实可以删除集合内的指定元素,并给一个返回值None。不过当集合内没有此元素的时候,就会报错,提示关键词错误。

那让我们再来看看discard()

# 使用discard
mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1}
res = mySets.discard('123')
print(mySets, f'res:{res}')
res = mySets.discard('aaa')
print(mySets, f'res:{res}')

---
{False, True, 3.1415, (1, 2, 3), 'love', 'abc', 123} res:None
{False, True, 3.1415, (1, 2, 3), 'love', 'abc', 123} res:None

remove()一样,也删除了一个指定元素,并且返回了None。不同的是,当我们使用discard删除一个不存在的元素时,discard虽然没有删除任何内容,但是也没有报错。

一个个删除太麻烦了,这个集合我就想让它变成一个空集合,好办,用clear()做清空处理呗,和字典一样:

mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1}
print(mySets)
mySets.clear()
print(mySets)

---
{False, True, 3.1415, (1, 2, 3), 'love', 'abc', 123, '123'}
set()

空集合拿到了,可以放入我们喜欢的元素了。依然和字典一致,我们可以使用update:

res = mySets.update({1, 2, 3, 4, 5})
print(mySets, f'res:{res}')
res = mySets.update({2, 3, 4, 5, 6})
print(mySets, f'res:{res}')

---
{1, 2, 3, 4, 5} res:None
{1, 2, 3, 4, 5, 6} res:None

结果中显示,我们更新成功了,新添加了一些元素进入集合。那第二次添加,为什么就只有6添加进去了呢?还记得么?集合不能有重复值,就跟字典不能有重复的key一样。在字典中使用update,遇到相同key后面的value会被更新,那其实集合也是一样的,只是因为只有一个值,所以更新完不还是这个值么。

在冰冻集合的时候我们用到过一次copy, 这里我们要单独拿出来说说,因为集合中的元素都是不可变的,包括元组和冰冻集合,所以当前集合的浅拷贝并不存在深拷贝的问题。换句话说,就是不存在在拷贝后,对集合中不可变的二级容器进行操作的问题。

mySets = {123,'abc',False,'love',True,(1,2,3),0,3.1415,'123',1}
res = mySets.copy()
print(res)

---
{False, True, 3.1415, (1, 2, 3), 'love', 'abc', 123, '123'}

集合是没有deepcopy方法的。

集合的运算和检测

集合的主要运算有四种,以下将列出这四种以及他们的方法:

  • 交集 &, set.intersection(), set.intersection_update()
  • 并集 |, union(), update()
  • 差集 -, difference(), difference_update()
  • 对称差集 ^, symmetric_difference(), symmetric_difference_update()

我们先来看看符号运算:

# 先定义两个集合
mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

然后让我们先求交集&:

# 求两个集合交集
res = mySet1 & mySet2
print(res)

---
{'Python'}

求两个集合并集(求并集的时候会去除重复项)|

res = mySet1 | mySet2
print(res)

---
{'Java', 'C', 'Rust', 'C++', 'Go', 'Python', 'Ruby', 'JavaScript', 'Swift'}

求两个集合差集-

res = mySet1 - mySet2
res2 = mySet2 - mySet1
print(res)
print(res2)

---
{'Go', 'Rust', 'C++', 'Swift'}
{'Java', 'C', 'Ruby', 'JavaScript'}

这段代码结果中resres2的区别在于,resmySet1中有,而mySet2中没有, res2mySet2中有,而mySet1中没有。

求两个集合对称差集^

res = mySet1 ^ mySet2
print(res)

---
{'Java', 'C', 'C++', 'Swift', 'Rust', 'Go', 'Ruby', 'JavaScript'}

看完符号运算,我们可以再来看看函数运算

交集的运算函数为set.intersection(), set.intersection_update(), 那这两个函数又有什么区别呢?

res = mySet1.intersection(mySet2)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
{'Python'}
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

让我们先记住intersection()的结果, mySet1mySet2并没有发生变化,而返回值为两个集合相同的内容。然后我们再来看看另外一个函数:

res = mySet1.intersection_update(mySet2)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
None
mySet1:{'Python'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

首先我们就能看到,返回值为None, 并且mySet1发生了变化。也就是说,set.intersection_update()是将两者的交集重复赋值给到了头部的变量,这里就是mySet1,然后返回一个None值。

接着我们来看一下并集运算函数: union(), update()

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet1.union(mySet2)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
{'Java', 'C', 'Rust', 'C++', 'Go', 'Python', 'Ruby', 'JavaScript', 'Swift'}
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

我们首先看到了返回值,正事两个集合的并集,两个原始集合也没有发生变化。

再来看看update()

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet1.update(mySet2)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
None
mySet1:{'Java', 'C', 'Rust', 'C++', 'Go', 'Python', 'Ruby', 'JavaScript', 'Swift'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

可以很明显看到区别:返回值为None,并集的计算结果被复制给了第一个变量,这里是mySet1

再看完并集之后,就轮到差集了, 分别是这两个函数difference(),difference_update()

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet1.difference(mySet2)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
{'Go', 'Rust', 'C++', 'Swift'}
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

返回值为差集的计算结果, 这里是mySet1有的而mySet2没有的。那不用问,按照一贯的惯例, difference_update()一定是将计算结果返回给第一个变量,这回我们换一下,将mySet2换成第一个变量试试:

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet2.difference_update(mySet1)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
None
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'Ruby', 'JavaScript'}

果然就跟料想的一样,最终的计算结果赋值给了mySet2

最后当然就是对称差集函数symmetric_difference() symmetric_difference_update()

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet2.symmetric_difference(mySet1)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
{'Java', 'C', 'C++', 'Swift', 'Rust', 'Go', 'Ruby', 'JavaScript'}
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'Python', 'Ruby', 'JavaScript'}

res接收了计算结果,成为了一个新集合。

接下来,大家应该能猜到了吧?不过还是要做做实验才知道,万一和自己想的不一样呢。

mySet1 = {'Python','Go','Rust', 'Swift', 'C++'}
mySet2 = {'C','JavaScript', 'Ruby', 'Java', 'Python'}

res = mySet2.symmetric_difference_update(mySet1)
print(res)
print(f'mySet1:{mySet1}, \nmySet2:{mySet2}')

---
None
mySet1:{'Go', 'Python', 'Rust', 'C++', 'Swift'},
mySet2:{'Java', 'C', 'C++', 'Swift', 'Rust', 'Go', 'Ruby', 'JavaScript'}

嗯,这个结果,一点都没有惊喜和意外。

好吧,运算我们学完之后,接着我们要看看集合的检测方法, 一共有三个,记住用法就可以了:

  • issuperset()检测是否为超集
  • issubset()检测是否为子集
  • isdisjoint()检测是否不相交

为了更好的说明,我们不能在用之前的集合,这回,我们定义三个集合来看:

mySet1 = {1, 2, 3, 4, 5, 6}
mySet2 = {1, 2, 3}
mySet3 = {6, 7, 8}

接下来从第一个检测开始:

print(mySet1.issuperset(mySet2))
print(mySet2.issuperset(mySet1))
print(mySet1.issuperset(mySet3))

---
True
False
False

不知道大家在数学里有没有学过「超集」的概念,我们从最后的打印结果可以看出来,mySet1mySet2的超集,反过来则不是,并且,mySet1也不是mySet3的超集。观察三个集合内的元素我们可以得出结论,如果集合a是另外一个集合b的超集,那么集合b内的元素一定在集合a中都找得到。

再来检测子集:

print(mySet1.issubset(mySet2))
print(mySet2.issubset(mySet1))
print(mySet3.issubset(mySet1))

---
False
True
False

从这个结果中我们能看到,子集的概念就和超集完全相反了。

最后就是检测两个集合是否相交了,也就是集合中的元素有没有重复的:

print(mySet1.isdisjoint(mySet2))
print(mySet2.isdisjoint(mySet1))
print(mySet3.isdisjoint(mySet1))
print(mySet2.isdisjoint(mySet3))

---
False
False
False
True

虽然前三个都打印的False, 最后一个打印的True,但是我们从集合中应该知道,只有mySet2mySet3没有相交关系。所以我们可以知道,isdisjoint()这个函数其实是检测不相交的。也就是说,返回结果为False则证明相交,返回结果为True反而是不相交。

结语

至此,随着我们的集合内容讲完,咱们的容器类数据类型就全部讲完了。

咱们下一节开始,咱们要开始行的篇章。下一节内容预告:Python中File文件的操作。

本节课一样就不布置作业了,大家好好的将最近将的容器类数据好好的回顾一下,将基础打扎实。

9會員
62內容數
从基础开始,再到Python,然后是CV、BI、NLP等相关技术。从头到尾详细的教授一边人工智能。
留言0
查看全部
發表第一個留言支持創作者!
茶桁的沙龍 的其他內容
Hi,大家好。我是茶桁。 关于Python的数据类型,我们已经详细讲解了三种,字符串,列表和元组。那么今天,我们再来讲一种:字典。 字典也是一种数据的集合,由健值对组成的数据集合,字典中的键是不能重复的。 字典中的键必须是不可变的数据类型,常用的键主要是:字符串,整型... 实际上,在之前字
Hi,大家好。我是茶桁。 之前两节分别介绍了字符串和列表,今天,我们来讲讲另外一个常用到的数据类型:元组。 元组和列表很像,两者都是一组有序的数据的组合。但是也有很多不同点,比如元组内的元素一旦定义了就不可以再修改,因此元组称为不可变数据类型。 元组定义 元组的定义方式包括以下要点: 定义
Hi,大家好。我是茶桁。 最近几节课,我们都是在详细讲解Python内的数据类型,上一节课我们详细了解了字符串,这节课,让我们来详解一下列表。 首先,我们先有一个大的概念,列表,其实就是一组有序的数据组合;另外,列表中的数据是可以被修改的。也就是说,列表是一个可变序列类型。 列表定义 如何在
Hi, 大家好。我是茶桁。 前几节课中我们学习了函数,那么这节课开始,我们花几节课返过头来详细的学习一下Python内的数据类型。第一节课,让我们先从字符串开始: 回顾字符串的定义方式 了解转义字符 字符串格式化的方法 字符串相关函数 字符串的定义方式 单引号定义字符串 ‘ ’ 双引
Hi,大家好。我是茶桁。 讲完了基础函数和高阶函数之后,我们这一节来研究下Python的内置函数,看看Python在安装完毕之后的解释器里,到底都预先给我们提供好了哪些可用的函数。 本节内容着重介绍一些常用函数,并且会做一些应用上的示例。当然,对于Python的内置函数,我们还可以查询官方文档,
Hi,大家好。 我是茶桁。 本节课,我们来学习一下Python中的「高阶函数」。 让我们先来了解一下,什么是递归函数。 递归函数就是定义一个函数,然后在此函数内,自己调用自己。 既然是自己调用自己,那这个函数必须要有一个结束才行,否则会一直重复的调用下去,直到调用层数越来越多,最
Hi,大家好。我是茶桁。 关于Python的数据类型,我们已经详细讲解了三种,字符串,列表和元组。那么今天,我们再来讲一种:字典。 字典也是一种数据的集合,由健值对组成的数据集合,字典中的键是不能重复的。 字典中的键必须是不可变的数据类型,常用的键主要是:字符串,整型... 实际上,在之前字
Hi,大家好。我是茶桁。 之前两节分别介绍了字符串和列表,今天,我们来讲讲另外一个常用到的数据类型:元组。 元组和列表很像,两者都是一组有序的数据的组合。但是也有很多不同点,比如元组内的元素一旦定义了就不可以再修改,因此元组称为不可变数据类型。 元组定义 元组的定义方式包括以下要点: 定义
Hi,大家好。我是茶桁。 最近几节课,我们都是在详细讲解Python内的数据类型,上一节课我们详细了解了字符串,这节课,让我们来详解一下列表。 首先,我们先有一个大的概念,列表,其实就是一组有序的数据组合;另外,列表中的数据是可以被修改的。也就是说,列表是一个可变序列类型。 列表定义 如何在
Hi, 大家好。我是茶桁。 前几节课中我们学习了函数,那么这节课开始,我们花几节课返过头来详细的学习一下Python内的数据类型。第一节课,让我们先从字符串开始: 回顾字符串的定义方式 了解转义字符 字符串格式化的方法 字符串相关函数 字符串的定义方式 单引号定义字符串 ‘ ’ 双引
Hi,大家好。我是茶桁。 讲完了基础函数和高阶函数之后,我们这一节来研究下Python的内置函数,看看Python在安装完毕之后的解释器里,到底都预先给我们提供好了哪些可用的函数。 本节内容着重介绍一些常用函数,并且会做一些应用上的示例。当然,对于Python的内置函数,我们还可以查询官方文档,
Hi,大家好。 我是茶桁。 本节课,我们来学习一下Python中的「高阶函数」。 让我们先来了解一下,什么是递归函数。 递归函数就是定义一个函数,然后在此函数内,自己调用自己。 既然是自己调用自己,那这个函数必须要有一个结束才行,否则会一直重复的调用下去,直到调用层数越来越多,最
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
在札記的最初,小編代表高中生看世界團隊,祝福讀者朋友們新年快樂!! 隨著時間邁入 2019 年,朝代的更迭也在世界各個角落上演。巴西的右派總統正式上任,宣示要建立一個沒有分歧的社會。 2019 年,也預祝各位的讀者的生涯,進入全新的篇章!
Thumbnail
政府每天都在做出選擇,面對人民所交出的權力,政府的選擇卻非總是依循人民的心意。對於這點,孟德斯鳩已經預見並提出解方: 「一切有權力的人都容易濫用權力,這是亙古不變的經驗。防止濫用權力的方法,就是以權力約束權力」。
Thumbnail
你知我知的順口溜 (笑),這句順口溜裡不經意地反映出我們對朝代更迭的渴望,每過一段時期,環境變了,舊時代必須褪去,披上新時代的新衣。在改朝換代的過程中,真的會那麼順利嗎? 
Thumbnail
時間飛逝,那麼快就12月了,我最近實在有太多太多的思緒。是時候拿出一張紙和筆,好好寫下自己腦袋裡的想法。畢竟,有時候自己覺得好像有很多東西需要整理,是因為自己沒有一個系統、不夠organized。
不用管我去哪裡 月圓時勢必在沙灘上 喝個醉爛如泥 醒來時化作你嘴邊咀嚼的那塊肉
Thumbnail
<p>陸穎魚的鏡頭,致她的日常/無常生活。</p>
Thumbnail
<p>陸穎魚的鏡頭,致她的日常/無常生活。</p>
Thumbnail
<p>陸穎魚的夢幻書單 (提早一點版本:)</p>
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
在札記的最初,小編代表高中生看世界團隊,祝福讀者朋友們新年快樂!! 隨著時間邁入 2019 年,朝代的更迭也在世界各個角落上演。巴西的右派總統正式上任,宣示要建立一個沒有分歧的社會。 2019 年,也預祝各位的讀者的生涯,進入全新的篇章!
Thumbnail
政府每天都在做出選擇,面對人民所交出的權力,政府的選擇卻非總是依循人民的心意。對於這點,孟德斯鳩已經預見並提出解方: 「一切有權力的人都容易濫用權力,這是亙古不變的經驗。防止濫用權力的方法,就是以權力約束權力」。
Thumbnail
你知我知的順口溜 (笑),這句順口溜裡不經意地反映出我們對朝代更迭的渴望,每過一段時期,環境變了,舊時代必須褪去,披上新時代的新衣。在改朝換代的過程中,真的會那麼順利嗎? 
Thumbnail
時間飛逝,那麼快就12月了,我最近實在有太多太多的思緒。是時候拿出一張紙和筆,好好寫下自己腦袋裡的想法。畢竟,有時候自己覺得好像有很多東西需要整理,是因為自己沒有一個系統、不夠organized。
不用管我去哪裡 月圓時勢必在沙灘上 喝個醉爛如泥 醒來時化作你嘴邊咀嚼的那塊肉
Thumbnail
<p>陸穎魚的鏡頭,致她的日常/無常生活。</p>
Thumbnail
<p>陸穎魚的鏡頭,致她的日常/無常生活。</p>
Thumbnail
<p>陸穎魚的夢幻書單 (提早一點版本:)</p>