集合(Set):去重、交集并集差集
一句话概述
集合(Set)是 Python 中专门用来去重和集合运算的数据结构——它和数学中的集合概念几乎一样。集合中的元素是唯一的(自动去重)、无序的、且必须是不可变类型。集合支持数学中的经典集合运算:交集(&,两个集合共有的元素)、并集(|,合并两个集合的所有元素)、差集(-,在 A 中但不在 B 中)、对称差集(^,只在其中一个集合中出现的元素)。在 AI 中,集合用于快速去重(如词汇表去重)、集合运算(如找出训练集和测试集重叠的样本)、成员检查(O(1) 的 in 操作)等场景。
💡 核心要点:①集合是无序、不重复元素的集合,用{}或set()创建,空集合必须用set()②集合自动去重——添加重复元素会被忽略 ③集合支持交集&、并集|、差集-、对称差集^运算 ④集合的in成员检查是 O(1),比列表的 O(n) 快得多 ⑤frozenset是不可变集合,可以作为字典的键或另一个集合的元素
教学与演示
一、集合的创建与去重——数学中的"不重复"集合
是什么:集合(set)是 Python 内置的可变集合类型,用花括号 {} 或 set() 函数创建。集合有三大特征:①无序——元素没有固定位置,不能用索引访问;②唯一——自动去除重复元素;③元素必须可哈希——和字典的键一样,只能存放不可变类型(整数、字符串、元组等)。
大白话 集合就像去超市购物时用的"品种清单"——你只关心"买了哪些品种",不关心"每种有多少个",也不关心"先拿的哪个后拿的哪个"。如果你不小心在清单上写了两次"苹果",集合自动把重复的划掉——只保留一个。这就是为什么集合最适合用来"去重"——把列表扔进集合,再拿出来,重复项就消失了。
为什么:去重是数据处理中最常见的需求之一。统计一篇文档中出现了哪些不同词汇、找出用户浏览过的所有品类、清理数据集中的重复样本——这些场景如果不用集合,就需要写循环+判断的冗余代码。集合让去重变成了一行代码。此外,集合的 O(1) 成员检查(in)在需要频繁判断"某个元素是否出现过"的场景中也远快于列表。
大白话 列表去重要写:unique = []; for x in data: if x not in unique: unique.append(x)——每次if x not in unique都要遍历整个 unique 列表,数据量大时慢得令人发指。集合一行搞定——unique = set(data)——底层哈希表直接去重,瞬间完成。
怎么做:
import numpy as np
# ====== 1. 创建集合 ======
# 方式一:花括号(不能用于空集合!)
fruits = {"苹果", "香蕉", "橘子", "苹果"} # 注意:写了两次"苹果"
print("水果集合:", fruits) # {'苹果', '香蕉', '橘子'} — 重复的自动去掉!
print("类型:", type(fruits).__name__) # set
# 方式二:set() 函数,从可迭代对象创建
chars = set("hello world") # 把字符串转成字符集合
print("字符集合:", chars) # {'h', 'e', 'l', 'o', ' ', 'w', 'r', 'd'}
# 注意:'l' 和 'o' 出现了多次,但集合里只保留一个
# ⚠️ 空集合必须用 set()!{} 是空字典!
empty_set = set() # ✅ 空集合
empty_dict = {} # ❌ 这是空字典!
print(f"\nset() 类型: {type(empty_set).__name__}") # set
print(f"{{}} 类型: {type(empty_dict).__name__}") # dict
# ====== 2. 集合的去重效果 ======
# 模拟:用户浏览记录的日志(有大量重复)
raw_logs = ["首页", "Python课程", "首页", "AI课程",
"Python课程", "首页", "关于我们", "Python课程"]
print(f"\n原始浏览记录 ({len(raw_logs)} 条): {raw_logs}")
# 一行去重!
unique_pages = set(raw_logs)
print(f"去重后页面 ({len(unique_pages)} 个): {unique_pages}")
# 8 条记录 → 4 个不同页面
# 统计独立访客
visitors = ["Alice", "Bob", "Alice", "Charlie", "Bob", "David", "Alice"]
unique_visitors = set(visitors)
print(f"\n独立访客: {unique_visitors}")
print(f"总访问次数: {len(visitors)}, 独立访客数: {len(unique_visitors)}")
# ====== 3. 集合元素的限制 ======
# ✅ 可以放:字符串、数字、元组(和字典的键一样)
valid_set = {1, "hello", 3.14, (1, 2)} # 不同类型混合(合法)
print(f"\n有效集合: {valid_set}")
# ❌ 不能放:列表、字典、集合本身(可变类型不可哈希)
# bad_set = {[1, 2], {"a": 1}} ❌ TypeError: unhashable type: 'list'
# ====== 4. 集合的基本操作 ======
s = {1, 2, 3}
print(f"\n初始集合: {s}")
# 添加元素
s.add(4) # add:添加单个元素
print(f"add(4): {s}")
s.add(3) # 添加已有元素——被忽略!不报错!
print(f"add(3)(重复): {s}") # 仍然是 {1, 2, 3, 4}
# 删除元素
s.remove(2) # remove:删除指定元素(不存在报错)
print(f"remove(2): {s}")
s.discard(5) # discard:删除指定元素(不存在不报错!)
print(f"discard(5)(不存在): {s}") # 不报错,但也没删除任何东西
# try:
# s.remove(5) # remove 不存在会报 KeyError
# except KeyError:
# print("remove(5) 报 KeyError")
# pop():随机删除并返回一个元素(因为集合无序)
popped = s.pop()
print(f"pop() 删除了: {popped}, 剩余: {s}")
# clear():清空
s.clear()
print(f"clear(): {s}") # set()
# ====== 5. 去重但保留顺序——列表去重的最佳实践 ======
# 问题:set() 去重会丢失顺序!
items = ["b", "a", "c", "b", "a", "d"]
print(f"\n有序去重:")
print(f" 原始: {items}")
print(f" set(): {list(set(items))}") # ['a', 'c', 'b', 'd'] — 顺序乱了!
# 解决方案:用 dict.fromkeys()(Python 3.7+ 字典有序)
ordered_unique = list(dict.fromkeys(items))
print(f" 保留顺序: {ordered_unique}") # ['b', 'a', 'c', 'd'] — 顺序保留!什么用:在 AI 数据预处理中,集合的去重能力是无价的。构建词表时:vocab = set(all_words) 一行去重,得到所有不重复的词;数据清洗时:unique_ids = set(df['user_id']) 统计独立用户数;特征工程中:检查某列的唯一值:set(df['category']) 了解有多少类别。集合的去重也是构建 one-hot 特征和标签编码的基础步骤。
二、集合运算——数学中的交集、并集、差集
是什么:集合支持四种经典的数学集合运算,每种有对应的运算符和方法两种写法。交集(& / .intersection()):两个集合共有的元素;并集(| / .union()):两个集合所有的元素(去重合并);差集(- / .difference()):在第一个集合中但不在第二个集合中的元素;对称差集(^ / .symmetric_difference()):只在其中一个集合中出现的元素(并集减去交集)。
大白话 把两个集合画成两个圆圈(韦恩图):交集是两个圆圈重叠的部分——"两者都有";并集是两个圆圈的全部区域——"任何一个有都算";差集是 A 圆圈中不和 B 重叠的部分——"A 有但 B 没有";对称差集是两个圆圈中不重叠的部分——"只在一边有,不同时有"。
为什么:集合运算是数据处理中"找共同点"和"找差异"的标准方法。找出同时购买了商品 A 和 B 的用户(交集)、合并两个数据源的所有条目(并集)、找出新增用户(差集)、发现数据源之间的差异(对称差集)。在 AI 中,这些运算常用于数据集划分验证、特征选择、标签分析等场景。
怎么做:
import numpy as np
# ====== 模拟:两个 AI 推荐系统的用户集合 ======
# 购买了"Python课程"的用户
python_users = {"Alice", "Bob", "Charlie", "David", "Eve"}
# 购买了"AI课程"的用户
ai_users = {"Bob", "Charlie", "Frank", "Grace", "Eve"}
print("Python课程用户:", python_users)
print("AI课程用户:", ai_users)
print()
# ====== 1. 交集 & —— 两个集合共有的 ======
# 两种写法:运算符 & 或方法 .intersection()
common = python_users & ai_users # 同时购买两门课程的用户
print(f"交集(同时购买两门课): {common}") # {'Bob', 'Charlie', 'Eve'}
print(f" → 交叉销售机会:{len(common)} 人")
# ====== 2. 并集 | —— 合并所有不重复元素 ======
all_users = python_users | ai_users # 至少购买了一门课程的所有用户
print(f"\n并集(至少购买一门课): {all_users}")
print(f" → 总覆盖用户:{len(all_users)} 人")
# ====== 3. 差集 - —— 在A中但不在B中 ======
only_python = python_users - ai_users # 只买了Python的用户
print(f"\n差集(只买Python): {only_python}") # {'Alice', 'David'}
print(f" → 可推销AI课程:{len(only_python)} 人")
only_ai = ai_users - python_users # 只买了AI的用户
print(f"差集(只买AI): {only_ai}") # {'Frank', 'Grace'}
print(f" → 可推销Python课程:{len(only_ai)} 人")
# ====== 4. 对称差集 ^ —— 只在其中一个集合中 ======
exclusive = python_users ^ ai_users # 只买一门课的用户
print(f"\n对称差集(只买一门课): {exclusive}")
print(f" → 可推销另一门课:{len(exclusive)} 人")
# ====== 5. 子集和超集判断 ======
subset = {"Bob", "Charlie"} # 小集合
print(f"\n子集判断:")
print(f" {subset} 是 Python用户 的子集? {subset.issubset(python_users)}")
# 等价于 subset <= python_users
print(f" ⊆ 运算符写法: {subset <= python_users}")
# 真子集(小于,不等于)
print(f" 真子集 < : {subset < python_users}")
# ====== 6. AI 实战:数据集划分验证 ======
# 模拟:训练集和测试集的样本 ID
train_ids = set(range(0, 800)) # 800 个训练样本
test_ids = set(range(750, 1000)) # 250 个测试样本(和训练集有 50 个重叠!)
print(f"\n=== 数据集划分验证 ===")
print(f"训练集样本数: {len(train_ids)}")
print(f"测试集样本数: {len(test_ids)}")
# 检查是否有数据泄漏(训练集和测试集不应该有重叠!)
leakage = train_ids & test_ids # 交集 = 重叠样本
if leakage:
print(f"⚠️ 数据泄漏!重叠样本数: {len(leakage)}")
print(f" 重叠 ID: {sorted(leakage)[:10]}...") # 显示前 10 个
else:
print("✅ 无数据泄漏,训练集和测试集完全独立")
# 修复:从测试集中移除重叠样本
test_ids_clean = test_ids - train_ids # 差集:测试集去掉训练集中出现的
print(f"清理后测试集: {len(test_ids_clean)} 个样本")
print(f"总计(并集): {len(train_ids | test_ids_clean)} 个独立样本")
# ====== 7. 多集合运算——链式操作 ======
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
c = {1, 3, 5, 7}
# 三个集合的交集
print(f"\n三集合交集: {a & b & c}") # {3}
# 复杂的链式运算
result = (a | b) - c # a和b的并集,再减去c
print(f"(a | b) - c = {result}") # {2, 4, 6}什么用:在 AI 项目中,集合运算场景极其常见。数据划分验证:检查训练/验证/测试集是否互斥(交集应为空);标签分析:set(predicted_labels) & set(true_labels) 检查预测覆盖了哪些真实类别;特征选择:差集找出数据集中独有特征;推荐系统:交集找共同兴趣,差集找差异化推荐机会。语义分割评估中的 IoU(交并比)本质上就是 |A ∩ B| / |A ∪ B|。
三、性能优势——为什么集合比列表快得多
是什么:集合底层使用哈希表实现,使其成员检查操作(x in set)的时间复杂度为 O(1)——无论集合多大,判断某个元素是否存在的速度几乎恒定。相比之下,列表的 in 操作是 O(n)——列表越大,查找越慢。这是因为集合通过哈希运算直接定位到元素可能的存储位置,而列表需要从头到尾逐个比对。
大白话 集合就像一个图书馆的"作者索引卡"——你要找"鲁迅"写的书,不用走遍所有书架,直接翻到索引卡 "L" 那一页,一秒定位。列表就像你把所有书堆在地上——要找"鲁迅",得一本一本地翻。书越堆越多,翻得越来越慢。
为什么:在 AI 数据处理中,经常需要判断"这个词是否在词表中"、"这个 ID 是否已处理过"、"这个样本是否在训练集中"。如果每次判断都用列表 O(n) 扫描,在大规模数据上性能会差几个数量级(百万级数据可能从几秒变成几分钟)。集合让这些高频成员检查操作保持恒定速度。
怎么做:
import numpy as np
import time
# ====== 1. 成员检查性能对比:列表 vs 集合 ======
# 创建测试数据:10 万个元素
size = 100_000
test_list = list(range(size))
test_set = set(range(size))
# 用 numpy 随机选择 1000 个要查找的元素
np.random.seed(42)
search_items = np.random.randint(0, size * 2, 1000).tolist()
# 注意:一半在数据中(0-size),一半不在(size-2*size)
# 列表查找计时
start = time.perf_counter()
list_results = [x in test_list for x in search_items]
list_time = time.perf_counter() - start
# 集合查找计时
start = time.perf_counter()
set_results = [x in test_set for x in search_items]
set_time = time.perf_counter() - start
print(f"=== 成员检查性能对比({size:,} 个元素,{len(search_items)} 次查找)===")
print(f"列表 in 操作: {list_time:.4f} 秒")
print(f"集合 in 操作: {set_time:.6f} 秒")
print(f"速度提升: {list_time / set_time:.0f} 倍")
# 结果:集合比列表快数百到数千倍!
# ====== 2. 去重性能对比 ======
# 生成含重复的 10 万条数据
np.random.seed(0)
data_dup = np.random.randint(0, 10000, 100_000).tolist() # 1 万种值,10 万个元素
# 列表方式去重
start = time.perf_counter()
unique_list = []
for x in data_dup:
if x not in unique_list: # 每次都要 O(n) 扫描!
unique_list.append(x)
list_dedup_time = time.perf_counter() - start
# 集合方式去重
start = time.perf_counter()
unique_set = set(data_dup) # O(n) 哈希去重
set_dedup_time = time.perf_counter() - start
print(f"\n=== 去重性能对比({len(data_dup):,} 条,{len(set(data_dup)):,} 种不同值)===")
print(f"列表方式: {list_dedup_time:.4f} 秒")
print(f"集合方式: {set_dedup_time:.6f} 秒")
print(f"速度提升: {list_dedup_time / set_dedup_time:.0f} 倍")
# ====== 3. AI 实战:高效去重 ======
# 场景:日志中有大量重复的用户行为,需要统计独立用户
# 模拟 100 万条行为日志中有重复的 10 万用户
np.random.seed(1)
user_ids = np.random.randint(10000, 110000, 1_000_000).tolist()
start = time.perf_counter()
unique_users = set(user_ids) # 一行代码,O(n) 完成
elapsed = time.perf_counter() - start
print(f"\n=== 大数据去重实战 ===")
print(f"总日志数: {len(user_ids):,}")
print(f"独立用户数: {len(unique_users):,}")
print(f"去重耗时: {elapsed:.4f} 秒")
# ====== 4. 集合推导式——和列表推导式一样优雅 ======
# 创建平方数集合(自动去重!)
squares = {x**2 for x in range(-5, 6)} # range(-5, 6) 有重复的平方值
print(f"\n平方数集合: {sorted(squares)}") # 0, 1, 4, 9, 16, 25 — 自动去重!
# 过滤:生成 1-20 中能被 3 整除的数的平方集合
filtered = {x**2 for x in range(1, 21) if x % 3 == 0}
print(f"能被3整除的数的平方: {sorted(filtered)}") # 9, 36, 81, 144, 225, 324什么用:在实际 AI 项目中,理解集合的性能优势可以帮你做出正确的数据结构选择。如果需要一个"检查某元素是否已存在"的数据结构,一定用集合而非列表——性能差距可能高达数千倍。在构建大型词表(NLP 中可能有数十万词汇)时,用集合维护"已见过的词"远比用列表高效。这看似微小的选择,在大规模数据上会带来显著的体验差异。
四、frozenset——不可变集合
是什么:frozenset 是集合的不可变版本——一旦创建就不能添加或删除元素。它和集合共享所有集合运算(交集、并集、差集等),但因为不可变,它是可哈希的——可以作为字典的键,也可以作为另一个集合的元素。
大白话 如果集合是"可擦写的白板",frozenset 就是"刻好的印章"——内容定死了不能改。正因为不能改,它可以安全地做字典的键和嵌套集合的元素,而普通 set 做不到。当你有一组不应被修改的固定集合时(如类别集合、配置选项),用 frozenset 就是声明"这组数据定死了,谁也别想改"。
为什么:在某些场景下,你需要用集合作为字典的键——比如用 frozenset(["猫", "狗"]) 作为某张图片包含的动物标签的键。普通集合无法胜任(因为可变)。此外,frozenset 也是一种"设计即文档"——看到 frozenset,就知道这组数据在程序运行中不会改变,增强了代码的可读性和安全性。
怎么做:
import numpy as np
# ====== 1. 创建 frozenset ======
# 从可迭代对象创建
fs1 = frozenset([1, 2, 3, 2, 1]) # 自动去重
print("frozenset:", fs1) # frozenset({1, 2, 3})
# 从集合创建
fs2 = frozenset({3, 4, 5})
print("frozenset2:", fs2)
# ====== 2. 不可变性——不能修改 ======
# fs1.add(4) ❌ AttributeError: 'frozenset' object has no attribute 'add'
# fs1.remove(1) ❌ AttributeError
# 但支持所有不修改的集合运算
print(f"\n交集: {fs1 & fs2}") # frozenset({3})
print(f"并集: {fs1 | fs2}") # frozenset({1, 2, 3, 4, 5})
print(f"差集: {fs1 - fs2}") # frozenset({1, 2})
# 注意:运算结果也是 frozenset!
# ====== 3. frozenset 可以哈希——做字典的键 ======
# 模拟:标签组合 → 样本数量 的统计
# 一张图片可能同时有"猫"和"狗"两种标签
tag_counts = {
frozenset(["猫"]): 100, # 只含猫标签的图片 100 张
frozenset(["狗"]): 80, # 只含狗标签的图片 80 张
frozenset(["猫", "狗"]): 30, # 同时含猫和狗的图片 30 张
frozenset(["猫", "狗", "鸟"]): 5, # 三种都含的图片 5 张
}
print("\n=== 标签组合统计 ===")
for tags, count in tag_counts.items():
tag_list = sorted(tags) # 排序便于阅读
print(f" 标签 {tag_list}: {count} 张")
# frozenset 还可以做集合的元素(嵌套集合)
nested_set = {frozenset({1, 2}), frozenset({3, 4})}
print(f"\n嵌套集合: {nested_set}")
# ====== 4. 普通 set 不能哈希 ======
# 这是做不到的:
# bad = {{1, 2}, {3, 4}} ❌ TypeError: unhashable type: 'set'
# ====== 5. AI 实战:用 frozenset 做缓存键 ======
# 场景:推荐系统中,根据"用户已购商品集合"查询推荐结果
# 集合中的元素顺序不影响推荐——{苹果, 香蕉} 和 {香蕉, 苹果} 应该返回相同推荐
recommendation_cache = {} # 缓存字典
def get_recommendations(purchased_set):
"""根据已购商品集合计算推荐(模拟)"""
key = frozenset(purchased_set) # 转成 frozenset 做键
if key in recommendation_cache:
return recommendation_cache[key] # 缓存命中
# 模拟复杂推荐计算
all_items = {"苹果", "香蕉", "橘子", "西瓜", "葡萄", "草莓"}
recs = all_items - key # 推荐没买过的
recommendation_cache[key] = recs
return recs
# 测试:不同顺序的相同集合应返回相同推荐
result1 = get_recommendations({"苹果", "香蕉"})
result2 = get_recommendations({"香蕉", "苹果"}) # 顺序不同但集合相同
print(f"\n推荐结果1: {result1}")
print(f"推荐结果2: {result2}")
print(f"两次结果相同: {result1 == result2}") # True
print(f"缓存大小: {len(recommendation_cache)}") # 1 — 只算了一次!什么用:在 AI 中,frozenset 适合表示"固定的集合型配置"。比如多标签分类中固定的标签集合、推荐系统中固定的商品类目、模型支持的数据类型集合。使用 frozenset 做缓存键特别实用——集合中元素的顺序不影响哈希值,frozenset({"a", "b"}) 和 frozenset({"b", "a"}) 是同一个键。这使得你可以安全地用"无序的集合"作为缓存依据,而不需要先排序。
概念关系图谱
| 概念 | 核心含义 | 与AI的关系 | 关联概念 |
|---|---|---|---|
| set | 无序、不重复、可变的集合 | 快速去重、词汇表构建 | frozenset、列表 |
| 去重 | 自动去除重复元素 | 词表构建、独立用户统计 | 唯一性、哈希 |
| 交集 & | 两个集合共有的元素 | 数据泄漏检测、共同特征 | 并集、差集 |
| 并集 | | 合并两个集合的所有元素 | 数据源合并、全量特征 | 交集、差集 |
| 差集 - | 在A中不在B中 | 新增用户识别、数据清理 | 对称差集 |
| 成员检查 | O(1) 判断元素是否存在 | 高频查找、词表查询 | 哈希表、in |
| frozenset | 不可变集合,可哈希 | 缓存键、固定类目配置 | 集合、不可变 |
| 哈希表 | 底层实现,快速定位 | O(1) 查找性能 | 字典、哈希函数 |
重点答疑
Q1: 集合和列表有什么区别?什么时候用集合?
核心区别:①列表有序、可重复、用索引访问;集合无序、不重复、不能索引。②列表可变,元素可以是任意类型;集合可变的但元素必须是不可变类型。③列表查找 O(n),集合查找 O(1)。用集合的场景:需要去重、需要快速成员检查(x in data)、需要做集合运算(交集/并集/差集)。用列表的场景:需要保持顺序、需要索引访问、允许重复元素。
Q2: 为什么空集合必须用 set() 而不能用 {}?
因为 {} 被 Python 解析器优先解释为空字典(dict)。这是历史遗留问题——字典比集合更早引入 Python,{} 的语法先被字典占用。集合是后来加入的,只能用 set() 创建空集合。这也解释了为什么 type({}) 返回 <class 'dict'> 而不是 <class 'set'>。创建非空集合时不会有歧义——{1, 2, 3} 没有冒号分隔,明确是集合。
Q3: 集合的 remove() 和 discard() 有什么区别?
remove(elem) 删除指定元素,如果元素不存在则抛出 KeyError。discard(elem) 也删除指定元素,但如果元素不存在则不报错,静默忽略。使用建议:如果你确定元素一定存在(不存在说明程序有 bug),用 remove() 让错误尽早暴露;如果你不确定元素是否存在(如用户输入),用 discard() 避免不必要的异常处理。
Q4: 集合去重后顺序乱了怎么办?如何保留顺序去重?
集合是无序的,去重时会丢失原始顺序。如果顺序重要,使用 dict.fromkeys() 技巧:list(dict.fromkeys(original_list))。因为 Python 3.7+ 的字典保持插入顺序,fromkeys() 创建字典时保留第一次出现的顺序,再转回列表即可。这是一个简洁且高效(O(n))的"有序去重"方法。或者使用 sorted(set(data), key=data.index),但这会是 O(n²) 不适合大数据。
Q5: 集合和字典有什么关系?为什么它们的查找都是 O(1)?
集合可以看作"只有键没有值"的字典。两者的底层实现都是哈希表——通过哈希函数把元素的哈希值映射到存储槽位。要查找元素时,先算哈希值,再直接定位到槽位——不需要遍历。这就是 O(1) 的原因。实际上,在 CPython 源码中,set 和 dict 共享大量相同的底层代码。理解这一点也有助于理解为什么两者的键/元素都必须是可哈希的。
章节单词汇总
| 英文 | 音标 | 术语/释义 |
|---|---|---|
| set | /set/ | 集合;无序、不重复元素的容器 |
| intersection | /ˌɪntərˈsekʃən/ | 交集;两个集合共有的元素 |
| union | /ˈjuːniən/ | 并集;两个集合的合并(去重) |
| difference | /ˈdɪfərəns/ | 差集;在A中但不在B中的元素 |
| symmetric | /sɪˈmetrɪk/ | 对称的;指对称差集运算 |
| frozenset | /ˈfroʊzən set/ | 冻结集合;不可变版本的集合 |
| hashable | /ˈhæʃəbl/ | 可哈希的;可作为集合元素 |
| deduplication | /diːˌdjuːplɪˈkeɪʃən/ | 去重;去除重复元素 |
面试练习
Q1 [单选] 以下哪个是正确的空集合创建方式?
- A.
s = {} - B.
s = set() - C.
s = set{} - D.
s = [ ]
解答:B 正确。{}创建的是空字典,不是空集合。set{}是语法错误。[]是空列表。空集合只能用set()创建。
Q2 [单选] s = {1, 2, 2, 3, 1, 4}; print(len(s)) 输出什么?
- A. 6
- B. 1
- C. 4
- D. 5
解答:C 正确。集合自动去重,{1, 2, 2, 3, 1, 4}等价于{1, 2, 3, 4},只有 4 个不重复元素。注意原始顺序也可能不保留。
Q3 [单选] a = {1, 2, 3}; b = {2, 3, 4}; print(a - b) 输出什么?
- A.
{1} - B.
{1, 4} - C.
{2, 3} - D.
{4}
解答:A 正确。差集a - b返回在 a 中但不在 b 中的元素,只有 1 满足。4 不在 a 中,不包含。注意差集不对称:b - a是{4}。
Q4 [多选] 以下哪些可以作为集合的元素?
- A.
"hello"(字符串) - B.
42(整数) - C.
(1, 2)(元组) - D.
[1, 2](列表)
解答:A、B、C 正确——它们都是不可变/可哈希类型。D 错误——列表是可变类型,不可哈希,不能放集合里。
Q5 [单选] 以下哪个集合运算返回两个集合共有的元素?
- A.
a | b - B.
a & b - C.
a - b - D.
a ^ b
解答:B 正确。&是交集运算符,返回两边都有的元素。|是并集(所有元素),-是差集(只在左边),^是对称差集(只在其中一边)。
Q6 [多选] 关于集合的 in 操作,以下说法正确的是?
- A. 集合的
in是 O(1),非常快 - B. 列表的
in是 O(n),比集合慢 - C. 集合的
in和列表的in速度一样 - D. 判断元素是否存在用集合比列表高效
解答:A、B、D 正确。C 错误——集合用哈希表实现 O(1),列表要逐个扫描 O(n),差距可达数千倍。
Q7 [单选] a = {1, 2}; a.add(2); print(a) 输出什么?
- A.
{1, 2} - B.
{1, 2, 2} - C.
{2, 1, 2} - D. 报错
解答:A 正确。向集合添加已有元素会被静默忽略,不报错。集合自动保持元素的唯一性。
Q8 [多选] 以下关于 frozenset 的说法正确的是?
- A. frozenset 是不可变的,创建后不能增删元素
- B. frozenset 支持交集、并集等集合运算
- C. frozenset 可以作为字典的键
- D. frozenset 可以用
add()方法添加元素
解答:A、B、C 正确。D 错误——frozenset 没有 add() 方法(不可变)。运算返回的也是 frozenset。
Q9 [单选] 如何高效地从列表中移除重复元素并保留原始顺序?
- A.
list(set(lst)) - B.
sorted(set(lst)) - C.
list(dict.fromkeys(lst)) - D.
list({*lst})
解答:C 正确。dict.fromkeys() 利用 Python 3.7+ 字典的有序性保留第一次出现的顺序。A 去重但丢失顺序;B 排序后顺序更乱;D 等同于 A(无序)。
Q10 [多选] a = {1, 2, 3, 4}; b = {3, 4, 5, 6},以下运算结果正确的是?
- A.
a & b→{3, 4} - B.
a | b→{1, 2, 3, 4, 5, 6} - C.
a ^ b→{1, 2, 5, 6} - D.
a - b→{5, 6}
解答:A、B、C 正确。D 错误——a - b是在 a 中不在 b 中的,结果是{1, 2},而{5, 6}是b - a。