集合(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) 删除指定元素,如果元素不存在则抛出 KeyErrordiscard(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