课程链接:头歌实践教学平台
7.1 集合(set)
集合类型用来保存无序的、不重复的不可变数据,其概念和数学中集合的概念基本一致,如果我们把一定范围的、确定的、可以区别的事物当作一个整体来看待,那么这个整体就是集合,集合中的各个事物称为集合的元素。通常,集合需要满足以下特性:
1.无序性:一个集合中,每个元素的地位都是相同的,元素之间是无序的。
2.互异性:一个集合中,任何两个元素都是不相同的,即元素在集合中只能出现一次,集合中不存在重复的元素。
3.确定性:给定一个集合和一个任意元素,该元素要么属这个集合,要么不属于这个集合,二者必居其一,不允许有模棱两可的情况出现。
set1 = {'Python', 'C++', 'Java', 'Kotlin', 'Swift'} # 集合 for elem in set1: # 遍历集合中的元素 print(elem,end=' ') # 每次运行结果可能不同 # C++ Swift Python Kotlin Java # Java Kotlin C++ Swift Python
无序性说明集合中的元素并不像列中的元素那样存在某种次序,可以通过索引运算就能访问任意元素,集合并不支持索引运算。
集合的互异性决定了集合中不能有重复元素,这一点也是集合区别于列表的地方,我们无法将重复的元素添加到一个集合中。集合类型必然是支持in和not in成员运算的,这样就可以确定一个元素是否属于集合,也就是上面所说的集合的确定性。
集合的成员运算在性能上要优于列表的成员运算,这是集合的底层存储特性决定的(集合底层使用了哈希存储(散列存储))
可近似将集合看成是没有值的字典。
常见的用途:
1.成员检测 2.从序列中去除重复项 3.数学中的集合类计算
目前有两种内置集合类型:
集合(set)
不可变集合(frozenset)
set和frozenset的关系类似于列表和元组的关系。
set 类型是可变但 unhashable的,其内容可以使用 add() 和 remove() 这样的方法来改变, 但不能被用作字典的键或其他集合的元素;
frozenset 类型是不可变并且为 hashable,因此它可以被用作字典的键或其他集合的元素。
你的理解基本正确,但需要更精确地区分术语和概念。以下是关于 set
和 frozenset
的详细解释,以及它们与列表(list
)和元组(tuple
)关系的类比分析:
1. set
和 frozenset
的核心区别
特性 | set |
frozenset |
---|---|---|
可变性(Mutability) | 可变:可动态添加/删除元素 | 不可变:创建后内容不可修改 |
哈希性(Hashability) | 不可哈希:不能作为字典的键或集合的元素 | 可哈希:可作为字典的键或集合的元素 |
方法 | 支持 add() , remove() , pop() 等 |
仅支持不可变操作(如 union() , intersection() ) |
2. 与 list
和 tuple
的类比关系
list
vstuple
:list
是可变的(支持增删改),tuple
是不可变的(创建后内容固定)。tuple
因不可变性而可哈希,可用作字典的键或集合的元素;list
不可哈希,不能直接用于这些场景。
set
vsfrozenset
:set
是可变的(支持add()
,remove()
),类似list
的动态性。frozenset
是不可变的(类似tuple
),但它的不可变性针对的是集合的成员而非顺序(集合本身是无序的)。frozenset
因不可变性而可哈希,可用作字典的键或集合的元素;set
不可哈希,不能直接用于这些场景。
3. 为什么 set
不可哈希?
- 哈希性要求对象不可变:
哈希值通常用于快速比较对象是否相等(如字典键查找)。若对象可变,其哈希值可能在比较过程中变化,导致逻辑错误。- 例如:若
set
可作为字典键,修改其内容会改变哈希值,但字典无法感知这种变化,导致键失效。
- 例如:若
set
的可变性:
set
支持动态操作(如add()
),因此不符合哈希性的要求。
4. 为什么 frozenset
可哈希?
- 不可变性保证哈希稳定:
frozenset
创建后内容不可变,其哈希值在生命周期内保持不变,因此可安全用作字典键或集合元素。- 示例:
python
fs = frozenset([1, 2, 3])
d = {fs: "value"} # 合法:frozenset 是可哈希的
- 示例:
5. 使用场景对比
场景 | set |
frozenset |
---|---|---|
动态去重 | 适合(如实时过滤重复数据) | 不适用(不可变) |
字典键或集合元素 | 不适用(会报错) | 适合(如记录唯一组合) |
函数参数传递 | 可能被意外修改 | 安全(内容不可变) |
6. 代码示例
python
# set 示例(可变,不可哈希) |
s = {1, 2, 3} |
s.add(4) # 合法 |
try: |
d = {s: "value"} # 报错:TypeError: unhashable type: 'set' |
except TypeError as e: |
print(e) |
# frozenset 示例(不可变,可哈希) |
fs = frozenset([1, 2, 3]) |
d = {fs: "value"} # 合法 |
print(d) # 输出: {frozenset({1, 2, 3}): 'value'} |
7. 总结
set
是动态、可变的无序集合,适合需要频繁修改的场景,但不可哈希。frozenset
是静态、不可变的无序集合,适合需要哈希稳定性的场景(如字典键或集合元素)。- 类比关系:
set:frozenset
类似于list:tuple
,均通过可变性/不可变性区分用途,但集合的“无序性”使其与序列类型的行为有本质差异。
1. 集合的创建
1.1 非空集合可通过将一系列用逗号分隔的数据放在一对大括号中的方法创建。
setA = {1, 2, 3, 4, 5} # 将集合数据赋值给变量,直接创建集合 print(setA) # 输出集合{1, 2, 3, 4, 5} setB = {'吉林', '武汉', '北京'} print(setB) # {'北京', '吉林', '武汉'} setC = {(1, 2, 3), 1, 2, 3.14, 'hello'} print(setC) # {(1, 2, 3), 1, 2, 3.14, 'hello'}

需要注意的是{}中需要至少有一个元素,不能直接使用“{}”来创建和表示空集合,“{}”将创建一个空字典
setD = {} # 将创建一个空字典 print(setD) print(type(setD)) # 输出类型为dict
集合元素必须为不可变类型**,如“{}”内包含可变类型,将会抛TypeError异常。
setC = {[1, 2, 3], 1, 2, 3.14, 'hello'} # 列表[1,2,3]为可变类型,抛TypeError异常
由于集合元素不重复,如包含重复元素,将会去重。
set_1 = {(1, 2, 3), 1, 1, 1, 1, 1, 2, 3.14, 'hello'} # 包含多个整数1,将会去重,最终字典中将只包含一个1 print(set_1) # {1, 2, 3.14, 'hello', (1, 2, 3)} set_2 = {0, 0.0 ,0j} # 由于数值上 0 == 0.0 == 0j,所以同样会去重 print(set_2) # {0}

1.2 使用 set() 函数可创建集合(set)
set()和 frozenset()函数又称为集合构造器,分别用来生成可变和不可变的集合。
如果提供一个参数,则该参数必须是可迭代的,即参数必须是序列、列表、元组、推导式、迭代器或字典等支持迭代的对象。
setE = set(range(8)) # 通过range创建集合 {0, 1, 2, 3, 4, 5, 6, 7}并赋值给 S5 print(setE) # 输出{0, 1, 2, 3, 4, 5, 6, 7} setF = set([1, 2, 3, 4, 5]) # set()将列表转为集合 {1, 2, 3, 4, 5} print(setF) # 输出{1, 2, 3, 4, 5} print(set('cheeseshop')) # 字符串转为集合,去掉重复元素, {'c', 'p', 'o', 'e', 's', 'h'}
1.3 使用 frozenset() 函数可创建不可变集合(frozenset)
setG = frozenset((1, 3, 5, 7)) # 用frozenset()函数将元组转为不可变集合 print(setG) # 输出 frozenset({1, 3, 5, 7})
如果不提供任何参数,默认会生成空集合。
setI = set() # 使用函数创建一个空集合,空集合不能使用{}创建和表示 print(setI) # 输出 set() setJ = frozenset() # 使用 frozenset()函数构造器创建一个空的不可变集合 print(setJ) # 输出 frozenset()
1.4 利用集合推导式
与列表推导式类似,集合也可以通过集合推导式创建。
集合推导式与列表推导式的唯一区别在于用{}代替[]。
setH = {i*i for i in range(5)} # 利用推导式生成集合 print(setH) # {0, 1, 4, 9, 16}
集合中的元素必须是hashable类型,使用哈希存储的容器都会对元素提出这一要求。
所谓hashable类型指的是能够计算出哈希码的数据类型,通常不可变类型都是hashable类型,如:
整数(int)、浮点小数(float)、布尔值(bool)、字符串(str)、元组(tuple)等。
可变类型都不是hashable类型,因为可变类型无法计算出确定的哈希码,所以它们不能放到集合中。例如:我们不能将列表作为集合中的元素;同理,由于集合本身也是可变类型,所以集合也不能作为集合中的元素。
我们可以创建出嵌套的列表,但是我们不能创建出嵌套的集合,这一点在使用集合的时候一定要引起注意。
1.5 集合变量也可以赋值给另一个变量
但这时,两个变量指向相同的内存,当一个集合元素发生变化时,另一个集合的元素同时也会发生变化。
如果需要创建的一个原集合内容一致的不同集合对象时,可以使用s.copy()的方法。
city_set = {'吉林', '武汉', '北京'} new_city_set = city_set # 将集合变量city_set赋值给new_city_set,此时两个变量指向相同的内存 city_set.add('深圳') # 为集合city_set添加一个元素'深圳',new_city_set也会同时变化 print(city_set) print(new_city_set)
如果需要创建的一个原集合内容一致的不同集合对象时,可以使用s.copy()的方法。
city_set = {'吉林', '武汉', '北京'} new_city_set = city_set.copy() # 创建的一个集合变量city_set内容一致的新集合对象new_city_set city_set.add('深圳') # 为集合city_set添加一个元素'深圳',不影响new_city_set print(city_set) print(new_city_set)
2. 集合去重特性的应用
由于集合(set)内的数据是不重复的,因此集合构造器常用来对其他的序列数据进行“去重操作”。
setA = set((90, 75, 88, 65, 90)) print(setA) # 输出{88, 65, 90, 75},重复的90被去掉 scores = [80, 85, 88, 93, 88, 81, 96, 73, 85, 77, 77, 86, 89, 68, 93, 82, 95, 81, 80, 70] # 输出不重复的4个最低分 [68, 70, 73, 77] # 用set()函数将score转为集合,同时去除重复的数字,list()函数再将集合转为列表 # 用sorted()函数对集合元素进行升序排序,再切片取排序后列表的前4个数 print(sorted(set(scores))[0:4])
集合支持Python内置函数 len(s),用于获取集合s中数据元素的个数。
可根据序列对象转换为集合后前后长度的变化判定其中是否存在重复元素。
实例7.1: 奇特的四位数
一个四位数,各位数字互不相同,所有数字之和等于6,并且这个数是11的倍数。满足这种要求的四位数有多少个?各是什么?
分析:
将这个四位数转为集合,如果各位上有相同数字存在,重复数字会被去掉,则生成的集合长度len(set(str(i)))必小于4,只有长度等于4的集合,其对应的数中才无重复数字。
所有数字之和等于6,并不需要从0000遍历到9999,只需要遍历到3210即可。
若这个数是11的倍数,则该数对11取模的值应该等于0。
其中map(int,list(str(i)))是将数字i 转为字符,再转为列表,map(int,ls)函数的作用是将序列ls中的每个元素映射为第一个参数指定的数据类型,此处是映射为整型。
# 输出各位数字互不相同、所有数字之和等于6,且是11的倍数的4位数 ls = [] for i in range(1000,3211): # 各位数加和为6的最大的无重复数字的数是3210 if i % 11 == 0 and sum(map(int,str(i))) == 6 and len(set(str(i))) == 4: ls.append(i) # 符合条件的数字加到列表ls里 print(len(ls)) # 列表长度就是符合条件的数字的个数 6 print(ls) # [1023, 1320, 2013, 2310, 3102, 3201]
实例7.2: 特殊的生日
每个日期可以转成8位数字,比如2018年5月12日 对应的就是 20180512。小明发现,自己的生日转成8位数字后,8个数字都没有重复,而且自他出生之后到今天,再也没有这样的日期了。请问小明的生日是哪天?
分析:
可以从当前日期开始向前逐日判定是否为满足条件的日期,为提高效率,可以略过一些不能表示合法日期的数字,例如月份位超过12的和日期位超过31的数字,同时确保最后得到的数字是合法日期。
start
:起始索引(包含该位置的字符)。end
:结束索引(不包含该位置的字符)。- 结果长度:
end - start
。