{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 集合(set)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"集合类型用来保存<font color='red'>**无序**</font>的、<font color='red'>**不重复**</font>的**<font color='red'>不可变数据**</font>,其概念和数学中集合的概念基本一致,如果我们把一定范围的、确定的、可以区别的事物当作一个整体来看待,那么这个整体就是集合,集合中的各个事物称为集合的元素。通常,集合需要满足以下特性:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"1.无序性:一个集合中,每个元素的地位都是相同的,元素之间是无序的。\n",
"2.互异性:一个集合中,任何两个元素都是不相同的,即元素在集合中只能出现一次,集合中不存在重复的元素。\n",
"3.确定性:给定一个集合和一个任意元素,该元素要么属这个集合,要么不属于这个集合,二者必居其一,不允许有模棱两可的情况出现。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Kotlin Java C++ Swift Python "
]
}
],
"source": [
"set1 = {'Python', 'C++', 'Java', 'Kotlin', 'Swift'} # 集合\n",
"\n",
"for elem in set1: # 遍历集合中的元素\n",
" print(elem,end=' ')\n",
"\n",
"# 每次运行结果可能不同\n",
"# C++ Swift Python Kotlin Java\n",
"# Java Kotlin C++ Swift Python "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"无序性说明集合中的元素并不像列中的元素那样存在某种次序,可以通过索引运算就能访问任意元素,集合并不支持索引运算。 \n",
"集合的互异性决定了集合中不能有重复元素,这一点也是集合区别于列表的地方,我们无法将重复的元素添加到一个集合中。集合类型必然是支持in和not in成员运算的,这样就可以确定一个元素是否属于集合,也就是上面所说的集合的确定性。 \n",
"集合的成员运算在性能上要优于列表的成员运算,这是集合的底层存储特性决定的(集合底层使用了哈希存储(散列存储))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"可近似将集合看成是<font color='red'>没有值的字典</font>。 \n",
"常见的用途: \n",
"1.成员检测\n",
"2.从序列中<font color='red'>去除重复项</font>\n",
"3.数学中的<font color='red'>集合类计算</font>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"目前有两种内置集合类型: \n",
"<font color='red'>**集合(set)** </font> \n",
"<font color='red'>**不可变集合(frozenset)**</font> "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"set和frozenset的关系类似于列表和元组的关系。 \n",
"set 类型是可变但 unhashable的,其内容可以使用 add() 和 remove() 这样的方法来改变, 但不能被用作字典的键或其他集合的元素; \n",
"frozenset 类型是不可变并且为 hashable,因此它可以被用作字典的键或其他集合的元素。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 集合的创建"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.1 非空集合可通过将一系列用逗号分隔的数据放在一对<font color='red'>**大括号**</font>中的方法创建。 "
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{1, 2, 3, 4, 5}\n",
"{'北京', '武汉', '吉林'}\n",
"{1, 2, 3.14, (1, 2, 3, 3), 'hello'}\n"
]
}
],
"source": [
"setA = {1, 2, 3, 4, 5} # 将集合数据赋值给变量,直接创建集合\n",
"print(setA) # 输出集合{1, 2, 3, 4, 5}\n",
"setB = {'吉林', '武汉', '北京'}\n",
"print(setB) # {'北京', '吉林', '武汉'}\n",
"setC = {(1, 2, 3,3), 1, 2, 3.14, 'hello',(1, 2, 3,3)}\n",
"print(setC) # {(1, 2, 3), 1, 2, 3.14, 'hello'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"images/ch7/1.png\" style=\"zoom:60%;\">"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"需要注意的是,。{}中需要至少有一个元素,不能直接使用<font color='red'>**“{}”**</font>来创建和表示<font color='red'>**空集合**</font>,“{}”将创建一个<font color='red'>**空字典**</font>"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{1}\n",
"<class 'set'>\n"
]
}
],
"source": [
"setD = {1} # 将创建一个空字典\n",
"print(setD) \n",
"print(type(setD)) # 输出类型为dict"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**集合元素必须为<font color='red'>**不可变类型**</font>,如“{}”内包含可变类型,将会抛TypeError异常。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"setC = {[1, 2, 3], 1, 2, 3.14, 'hello'} # 列表[1,2,3]为可变类型,抛TypeError异常"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由于集合元素<font color='red'>**不重复**</font>,如包含重复元素,将会<font color='red'>**去重**</font>。"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{1, 2, 3.14, (1, 2, 3), 'hello'}\n",
"{0}\n"
]
}
],
"source": [
"set_1 = {(1, 2, 3), 1, 1, 1, 1, 1, 2, 3.14, 'hello'} # 包含多个整数1,将会去重,最终字典中将只包含一个1\n",
"print(set_1) # {1, 2, 3.14, 'hello', (1, 2, 3)}\n",
"set_2 = {0, 0.0 ,0j} # 由于数值上 0 == 0.0 == 0j,所以同样会去重\n",
"print(set_2) # {0}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"images/ch7/2.png\" style=\"zoom:60%;\">"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.2 使用 set() 函数可创建集合(set)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"set()和 frozenset()函数又称为集合构造器,分别用来生成可变和不可变的集合。 \n",
"如果提供一个参数,则该<font color='red'>**参数**</font>必须是可迭代的,即参数必须是序列、列表、元组、推导式、迭代器或字典等支持<font color='red'>**迭代的**</font>对象。"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{0, 1, 2, 3, 4, 5, 6, 7}\n",
"{1, 2, 3, 4, 5}\n",
"{'p', 's', 'h', 'e', 'o', 'c'}\n"
]
}
],
"source": [
"setE = set(range(8)) # 通过range创建集合 {0, 1, 2, 3, 4, 5, 6, 7}并赋值给 S5\n",
"print(setE) # 输出{0, 1, 2, 3, 4, 5, 6, 7}\n",
"\n",
"setF = set([1, 2, 3, 4, 5]) # set()将列表转为集合 {1, 2, 3, 4, 5}\n",
"\n",
"print(setF) # 输出{1, 2, 3, 4, 5}\n",
"\n",
"print(set('cheeseshop')) # 字符串转为集合,去掉重复元素, {'c', 'p', 'o', 'e', 's', 'h'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.3 使用 frozenset() 函数可创建不可变集合(frozenset)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"setG = frozenset((1, 3, 5, 7)) # 用frozenset()函数将元组转为不可变集合\n",
"print(setG) # 输出 frozenset({1, 3, 5, 7})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如果不提供任何参数,默认会生成<font color='red'>**空集合**</font>。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"setI = set() # 使用函数创建一个空集合,空集合不能使用{}创建和表示\n",
"print(setI) # 输出 set()\n",
"setJ = frozenset() # 使用 frozenset()函数构造器创建一个空的不可变集合\n",
"print(setJ) # 输出 frozenset()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.4 利用集合推导式"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"与列表推导式类似,集合也可以通过集合推导式创建。 \n",
"集合推导式与列表推导式的唯一区别在于用<font color='red'>**{}**</font>代替\\[\\]。 "
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{0, 1, 4, 9, 16}\n"
]
}
],
"source": [
"setH = {i*i for i in range(5)} # 利用推导式生成集合\n",
"print(setH) # {0, 1, 4, 9, 16}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"集合中的元素必须是hashable类型,使用哈希存储的容器都会对元素提出这一要求。 \n",
"所谓hashable类型指的是能够计算出哈希码的数据类型,通常不可变类型都是hashable类型,如: \n",
"整数(int)、浮点小数(float)、布尔值(bool)、字符串(str)、元组(tuple)等。 \n",
"\n",
"可变类型都不是hashable类型,因为可变类型无法计算出确定的哈希码,所以它们不能放到集合中。例如:我们不能将列表作为集合中的元素;同理,由于集合本身也是可变类型,所以集合也不能作为集合中的元素。 \n",
"我们可以创建出嵌套的列表,但是我们不能创建出嵌套的集合,这一点在使用集合的时候一定要引起注意。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1.5 集合变量也可以赋值给另一个变量"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"但这时,两个变量指向相同的内存,当一个集合元素发生变化时,另一个集合的元素同时也会<font color='red'>**发生变化**</font>。 \n",
"如果需要创建的一个原集合内容一致的<font color='red'>**不同集合对象**</font>时,可以使用<font color='red'>**s.copy()**</font>的方法。"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'北京', '武汉', '吉林', '深圳'}\n",
"{'北京', '武汉', '吉林', '深圳'}\n"
]
}
],
"source": [
"city_set = {'吉林', '武汉', '北京'}\n",
"new_city_set = city_set # 将集合变量city_set赋值给new_city_set,此时两个变量指向相同的内存\n",
"city_set.add('深圳') # 为集合city_set添加一个元素'深圳',new_city_set也会同时变化\n",
"print(city_set)\n",
"print(new_city_set)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如果需要创建的一个原集合内容一致的不同集合对象时,可以使用s.copy()的方法。"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'北京', '武汉', '吉林', '深圳'}\n",
"{'北京', '武汉', '吉林'}\n"
]
}
],
"source": [
"city_set = {'吉林', '武汉', '北京'}\n",
"new_city_set = city_set.copy() # 创建的一个集合变量city_set内容一致的新集合对象new_city_set\n",
"city_set.add('深圳') # 为集合city_set添加一个元素'深圳',不影响new_city_set\n",
"print(city_set)\n",
"print(new_city_set)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. 集合去重特性的应用"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"由于集合(set)内的数据是<font color='red'>**不重复**</font>的,因此集合构造器常用来对其他的序列数据进行<font color='red'>**“去重操作”**</font>。"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{88, 65, 90, 75}\n",
"[68, 70, 73, 77]\n"
]
}
],
"source": [
"setA = set((90, 75, 88, 65, 90))\n",
"print(setA) # 输出{88, 65, 90, 75},重复的90被去掉\n",
"scores = [80, 85, 88, 93, 88, 81, 96, 73, 85, 77, 77, 86, 89, 68, 93, 82, 95, 81, 80, 70]\n",
"# 输出不重复的4个最低分 [68, 70, 73, 77]\n",
"# 用set()函数将score转为集合,同时去除重复的数字,list()函数再将集合转为列表\n",
"# 用sorted()函数对集合元素进行升序排序,再切片取排序后列表的前4个数\n",
"print(sorted(set(scores))[0:4])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<img src=\"images/ch7/3.png\" style=\"zoom:40%;\">"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"集合支持Python内置函数 len(s),用于获取集合s中数据元素的个数。 \n",
"可根据序列对象转换为集合后前后<font color='red'>**长度的变化**</font>判定其中是否存在<font color='red'>**重复元素**</font>。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 实例7.1: 奇特的四位数"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"一个四位数,各位数字互不相同,所有数字之和等于6,并且这个数是11的倍数。满足这种要求的四位数有多少个?各是什么?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**分析**: \n",
"将这个四位数转为集合,如果各位上有相同数字存在,重复数字会被去掉,则生成的集合长度len(set(str(i)))必小于4,只有长度等于4的集合,其对应的数中才无重复数字。 \n",
"所有数字之和等于6,并不需要从0000遍历到9999,只需要遍历到3210即可。 \n",
"若这个数是11的倍数,则该数对11取模的值应该等于0。 \n",
"其中map(int,list(str(i)))是将数字i 转为字符,再转为列表,map(int,ls)函数的作用是将序列ls中的每个元素映射为第一个参数指定的数据类型,此处是映射为整型。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 输出各位数字互不相同、所有数字之和等于6,且是11的倍数的4位数\n",
"ls = []\n",
"for i in range(1000,3211): # 各位数加和为6的最大的无重复数字的数是3210\n",
" if i % 11 == 0 and sum(map(int,str(i))) == 6 and len(set(str(i))) == 4:\n",
" ls.append(i) # 符合条件的数字加到列表ls里\n",
"print(len(ls)) # 列表长度就是符合条件的数字的个数 6\n",
"print(ls) # [1023, 1320, 2013, 2310, 3102, 3201]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 实例7.2: 特殊的生日"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"每个日期可以转成8位数字,比如2018年5月12日 对应的就是 20180512。小明发现,自己的生日转成8位数字后,8个数字都没有重复,而且自他出生之后到今天,再也没有这样的日期了。请问小明的生日是哪天?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**分析**: \n",
"可以从当前日期开始向前逐日判定是否为满足条件的日期,为提高效率,可以略过一些不能表示合法日期的数字,例如月份位超过12的和日期位超过31的数字,同时确保最后得到的数字是合法日期。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 从当前日期向前查找第一个没有重复数字的日期\n",
"day = 20200202 # 当前日期,整型\n",
"while True:\n",
" day = day - 1 # 从当前日期向前逐日判定\n",
" strDay = str(day) # 整型转字符串\n",
" if int(strDay[4:6])>13:\n",
" continue # 忽略月份超过12的数字\n",
" elif strDay[4:6] in ['01','03','05','07','08','10','12'] and int(strDay[-2:]) > 31:\n",
" continue # 以上月份,忽略超过31的日期\n",
" elif strDay[4:6] in ['04','06','09','11'] and int(strDay[-2:]) > 30:\n",
" continue # 以上月份,忽略超过30的日期\n",
" elif strDay[4:6] in ['02'] and int(strDay[-2:]) > 19:\n",
" continue # 2月份,忽略超过19的日期,20以后有重复数字\n",
" elif len(set(strDay)) == 8: # 字符串转集合,判断无重复数字是否8个\n",
" print(strDay)\n",
" break # 找到第一个满足条件的日期中止循环 19870625"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"上面的代码使用了大量的分支结构,用于排除不合法的日期,程序较复杂。 \n",
"这个问题也可以借助datetime库里的一些方法实现: \n",
"获取今天日期的方法是`datetime.now()`,可得到形如2018-09-04的日期。 \n",
"返回日期间隔的方法是`timedelta(days=1)`,括号中的`days=1`表示日期间隔为1天。 \n",
"从当前天向前查看每一天的日期中是否有重复数字,第一个出现的没有重复数字的日期就是答案。 \n",
"判断日期中是否有重复数字可用集合方法。\n",
"\n",
"使用datetime库简化后的代码如下:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 从当前日期向前查找第一个没有重复数字的日期\n",
"import datetime\n",
"\n",
"todays = datetime.datetime.now() # 获取今天日期形如:2020-02-02\n",
"while True:\n",
" todays = todays -datetime.timedelta(days=1) # 从今天起日期依次减一天\n",
" sday = todays.strftime('%Y%m%d') # 获得格式化的时间,形如:20190701\n",
" if len(set(sday)) == 8: # 字符串转集合,判断无重复数字是否8个\n",
" print('出生日期是{}年{}月{}日'.format(sday[:4],sday[4:6],sday[6:])) \n",
" break # 找到一个符合条件的日期就结束循环"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"datetime是一个非常有用的内置库,几乎和日期或时间相关的操作都可以找到相关的函数或方法,遇到相关的需求可查阅[datetime库官方文档](https://docs.python.org/zh-cn/3/library/datetime.html)获取帮助。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<font face='楷体' color='red' size=5> 练一练 </font>\n",
"\n",
"##### 猜年龄\n",
"\n",
"美国数学家维纳(N.Wiener)智力早熟,11岁就上了大学。他曾在1935~1936年应邀来中国清华大学讲学。一次,他参加某个重要会议,年轻的脸孔引人注目。于是有人询问他的年龄,他回答说:“我年龄的立方是个4位数。我年龄的4次方是个6位数。这10个数字正好包含了从0到9这10个数字,每个都恰好出现1次。” 请编程输出当年维纳的年龄。\n",
"\n",
"本题没有输入,输出应为18(维纳当年的年龄)\n",
"\n",
"**提示**:循环遍历所有可能的年龄,将年龄值的立方和4次方的结果转换为字符串并拼接起来,若该字符串转化为集合后长度长度仍为10,则是正确的年龄。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 请在此处编写代码,完成上面的题目的要求\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
}