06.元组和集合:Python 里最被低估的两个数据结构
·
元组和集合:Python 里最被低估的两个数据结构
新人学 Python,上来就是列表字典,元组和集合基本跳过。
说实话,这俩东西你用好了,代码能少写不少 bug,性能还能提一截。
元组:不可变的列表
元组用圆括号:
point = (3, 4)
person = ("张三", 28, "北京")
empty = ()
single = (1,) # 单个元素的元组,逗号不能省!
和列表最大的区别:元组创建后不能修改。
nums = (1, 2, 3)
# nums[0] = 99 # TypeError! 元组不能改
# nums.append(4) # AttributeError! 没有 append
那既然有列表了,为什么还要元组?
原因1:保护数据不被意外修改
# 这个函数返回的坐标就不该被调用方改
def get_mouse_position():
return (150, 300) # 元组
# 用列表的话,外面不小心 append 一下,数据就脏了
原因2:元组可以当字典的键
# 用元组存坐标 → 字典键
locations = {
(39.9, 116.4): "北京",
(31.2, 121.5): "上海",
(22.5, 114.1): "深圳",
}
print(locations[(31.2, 121.5)]) # 上海
列表是可变的,不能当键。元组不可变,可以。
原因3:函数返回多个值用的就是元组
def get_user():
return "张三", 28, "北京" # 其实返回的是元组
name, age, city = get_user() # 解包
print(type(get_user())) # <class 'tuple'>
原因4:元组比列表快一点、省一点内存
数据量大的时候能感觉到差异。不过对新手来说前面三条更重要。
元组的操作
t = (1, 2, 3, 2, 4)
print(len(t)) # 5
print(t[0]) # 1
print(t[1:3]) # (2, 3)
print(t.count(2)) # 2 — 统计出现次数
print(t.index(3)) # 2 — 查找位置
print(3 in t) # True
print(max(t)) # 4
print(sum(t)) # 12
# 解包
a, b, c = (1, 2, 3)
a, *rest, c = (1, 2, 3, 4, 5) # a=1, rest=[2,3,4], c=5
集合:自动去重 + 数学运算
集合用花括号(但空花括号是字典,空集合用 set()):
fruits = {"苹果", "香蕉", "橘子"}
empty = set() # 空集合,{} 是空字典
集合三大特点:
- 元素不重复(自动去重)
- 元素无序(没有索引)
- 元素必须可哈希(不可变类型,如数字、字符串、元组)
去重:一行搞定
nums = [1, 2, 2, 3, 3, 3, 4, 5, 5]
unique = list(set(nums))
print(unique) # [1, 2, 3, 4, 5] 顺序可能不同
比手写循环判重优雅太多了。
增删查
s = {1, 2, 3}
s.add(4) # 加一个
s.add(2) # 重复的不会加进去
s.update([5, 6]) # 批量加
s.remove(3) # 删除,不存在会报错
s.discard(10) # 删除,不存在也不报错
s.pop() # 随机删一个(因为无序)
print(3 in s) # 查 — 集合的查找是 O(1),比列表的 O(n) 快
数学运算:集合的灵魂
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # {1,2,3,4,5,6} — 并集
print(a & b) # {3,4} — 交集
print(a - b) # {1,2} — 差集(a有b没有)
print(b - a) # {5,6} — 差集(b有a没有)
print(a ^ b) # {1,2,5,6} — 对称差集(只在一边的)
这玩意儿在做"两个列表的交集/差集"时特别好用:
yesterday_visitors = {"张三", "李四", "王五"}
today_visitors = {"李四", "王五", "赵六"}
new_users = today_visitors - yesterday_visitors # {"赵六"}
returning = yesterday_visitors & today_visitors # {"李四", "王五"}
lost = yesterday_visitors - today_visitors # {"张三"}
什么时候用什么?
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 需要增删改 | 列表 | 可变 |
| 数据不该被改 | 元组 | 不可变,安全 |
| 字典的键 | 元组 | 不可变 |
| 要去重 | 集合 | 自动去重 |
| 要快速查找 | 集合 | O(1) |
| 两个序列的交差并 | 集合 | 数学运算 |
| 保持顺序 | 列表/元组 | 集合无序 |
一句话总结:列表是主力,元组保安全,集合做去重和运算。
新手常见坑
坑1:单个元素元组忘加逗号
t = (1) # 这不是元组,是整数 1(括号被当成运算优先级)
print(type(t)) # <class 'int'>
t = (1,) # 这才是元组
print(type(t)) # <class 'tuple'>
坑2:集合放不了可变元素
# s = {[1, 2], [3, 4]} # TypeError! 列表不可哈希
s = {(1, 2), (3, 4)} # 用元组就行
坑3:空集合用 {} 是字典
d = {}
print(type(d)) # <class 'dict'>
s = set()
print(type(s)) # <class 'set'>
记住:空集合 = set(),空字典 = {}。
动手试试
- 用集合去重:给一句话,输出不重复的字符
- 求出两个班级名单的公共学生和独有学生
- 用元组存储多个坐标点,计算离原点最近的点
参考答案:
# 1. 去重
sentence = "hello world"
unique_chars = set(sentence)
print(unique_chars)
# 2. 班级名单
class_a = {"张三", "李四", "王五", "赵六"}
class_b = {"李四", "赵六", "孙七", "周八"}
print("公共:", class_a & class_b)
print("A有B没有:", class_a - class_b)
# 3. 最近点
import math
points = [(1, 2), (3, 4), (0, 1), (5, 0)]
nearest = min(points, key=lambda p: math.sqrt(p[0]**2 + p[1]**2))
print(nearest) # (0, 1)
写在最后
元组和集合是两个"平时不起眼,用上了真香"的数据结构。尤其是集合的去重和数学运算,很多新手用列表+循环硬写几十行的事,集合一行就能搞定。
下一篇聊条件判断——if、elif、else,让你的代码开始做决策。
所有评论(0)