跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python算法

Python 核心数据结构:集合与冻结集合详解

介绍 Python 中集合(set)与冻结集合(frozenset)的核心特性与用法。集合具有无序且唯一的元素特征,支持高效的成员检查及并集、交集、差集等数学运算。文章涵盖创建、增删改查、关系判断等操作,并重点讲解不可变的 frozenset 如何作为字典键使用。最后通过去重、多列表对比、文本过滤及社交网络分析等实例,展示集合在实际开发中的应用价值。

深海蔚蓝发布于 2026/3/23更新于 2026/10/728K 浏览

第 4 章:无序且唯一的集合:集合与冻结集合

章节介绍

Python 中,除了列表和元组这类有序的序列,还有一类非常实用的无序容器:集合。集合最核心的特征是它的元素是唯一且无序的。想象一下,当你需要记录一批用户的唯一标签,或者快速比对两份数据之间的差异时,集合就能大显身手。它与数学中的集合概念高度一致,支持交集、并集等运算,处理这类问题既直观又高效。

创建一个集合很简单,可以直接用花括号 {},或者使用 set() 函数。但更常见的情况是,我们从已有的数据(比如一个可能包含重复项的列表)中提取唯一元素。这时,集合的'唯一性'就派上了用场。

def create_set_from_list(data_list: list) -> set:
    """ 从给定的列表创建一个集合。
    集合会自动去除列表中的重复元素,并失去原有的顺序。
    这是演示集合创建和其'唯一性'核心特性的基本示例。
    参数:
        data_list (list): 可能包含重复元素的输入列表。
    返回:
        set: 由输入列表元素构成的新集合。
    """
    # 使用 set() 构造函数直接转换列表,重复项将被自动去除
    result_set = set(data_list)
    return result_set

来轻松实现这一点,它会自动滤掉所有重复的内容。让我们看一个更直观的例子:

def demonstrate_set_uniqueness():
    """ 展示集合的核心特性:无序且唯一。
    通过对比转换前后的数据,直观显示重复元素如何被自动去除。
    """
    # 创建一个包含多个重复项的列表
    my_list = [1, 2, 2, 3, 4, 4, 4, 5, 'apple', 'banana', 'apple']
    print("原始列表(可能包含重复项):")
    print(f"{my_list}")
    print(f"列表长度:{len(my_list)}")
    # 将列表转换为集合
    my_set = set(my_list)
    print("\n转换后的集合(自动去重):")
    ()
    ()
    ()
print
f"{my_set}"
print
f"集合长度:{len(my_set)}"
print
"\n结论:集合自动删除了所有重复的 '2', '4' 和 'apple'。"

运行后你会发现,一个满是重复数字的列表,在转换为集合后,每个数字只保留了一个。

集合是可变的,这意味着你可以随时向其中添加或删除元素。

def basic_set_operations(initial_set: set, element_to_add, element_to_remove):
    """ 在一个初始集合上执行一系列基础操作。
    这些操作是使用集合数据结构时最常见的。
    参数:
        initial_set (set): 初始的集合。
        element_to_add: 要添加到集合中的元素。
        element_to_remove: 要从集合中尝试移除的元素。
    返回:
        set: 执行操作后最终得到的集合。
    """
    working_set = initial_set.copy()  # 创建副本以避免修改原集合
    print(f"初始集合:{working_set}")
    # 1. 添加元素
    print(f"\n1. 添加元素 '{element_to_add}'...")
    working_set.add(element_to_add)
    print(f" 添加后:{working_set}")
    # 2. 移除元素(如果存在)
    print(f"\n2. 尝试移除元素 '{element_to_remove}'...")
    if element_to_remove in working_set:
        working_set.remove(element_to_remove)
        print(f" 成功移除。移除后:{working_set}")
    else:
        print(f" 元素 '{element_to_remove}' 不在集合中,移除失败。")
    # 3. 安全的移除方法(discard)
    print(f"\n3. 使用 discard() 安全移除 '{element_to_remove}' (如果存在)...")
    working_set.discard(element_to_remove)  # 元素不存在也不会报错
    print(f" 操作后:{working_set}")
    # 4. 成员检查
    test_element = element_to_add
    print(f"\n4. 检查元素 '{test_element}' 是否在集合中...")
    is_present = test_element in working_set
    print(f" 结果:{is_present}")
    # 5. 清空集合
    print(f"\n5. 清空集合...")
    working_set.clear()
    print(f" 清空后:{working_set}")
    return working_set

展示了如何进行这些基础操作,比如添加一个元素、移除一个元素、检查某个元素是否存在,或者清空整个集合。这些操作让你能动态地管理一个不重复的数据池。

集合真正的威力在于它的数学运算。假设你有两个集合,A 代表小明喜欢的电影,B 代表小红喜欢的电影。如何找出他们共同喜欢的电影(交集)?或者他们所有喜欢的电影合集(并集)?Python 为集合提供了非常直观的运算符和方法。

求并集(union),即合并所有不重复的元素:

def perform_set_union(set_a: set, set_b: set) -> set:
    """ 计算并返回两个集合的并集。
    并集包含所有属于 set_a 或 set_b 的元素。
    参数:
        set_a (set): 第一个集合。
        set_b (set): 第二个集合。
    返回:
        set: set_a 和 set_b 的并集。
    """
    # 方法 1: 使用 union() 方法
    union_by_method = set_a.union(set_b)
    # 方法 2: 使用 | 运算符
    union_by_operator = set_a | set_b  # 两种方法结果应相同
    print(f"集合 A: {set_a}")
    print(f"集合 B: {set_b}")
    print(f"并集 (使用 union()): {union_by_method}")
    print(f"并集 (使用 | 运算符): {union_by_operator}")
    print(f"两种方法结果一致吗? {union_by_method == union_by_operator}")
    return union_by_method

求交集(intersection),找出共同拥有的元素:

def perform_set_intersection(set_a: set, set_b: set) -> set:
    """ 计算并返回两个集合的交集。
    交集包含所有同时属于 set_a 和 set_b 的元素。
    参数:
        set_a (set): 第一个集合。
        set_b (set): 第二个集合。
    返回:
        set: set_a 和 set_b 的交集。
    """
    # 方法 1: 使用 intersection() 方法
    intersection_by_method = set_a.intersection(set_b)
    # 方法 2: 使用 & 运算符
    intersection_by_operator = set_a & set_b
    print(f"集合 A: {set_a}")
    print(f"集合 B: {set_b}")
    print(f"交集 (使用 intersection()): {intersection_by_method}")
    print(f"交集 (使用 & 运算符): {intersection_by_operator}")
    return intersection_by_method

求差集(difference),找出'我有但你没有'的元素:

def perform_set_difference(set_a: set, set_b: set) -> set:
    """ 计算并返回两个集合的差集 (A - B)。
    差集包含所有属于 set_a 但不属于 set_b 的元素。
    注意:差集运算不具有交换律,A - B 与 B - A 不同。
    参数:
        set_a (set): 被减集合。
        set_b (set): 减去的集合。
    返回:
        set: set_a 和 set_b 的差集 (A - B)。
    """
    # 计算 A - B
    difference_ab_method = set_a.difference(set_b)
    difference_ab_operator = set_a - set_b
    # 计算 B - A 以作对比
    difference_ba = set_b - set_a
    print(f"集合 A: {set_a}")
    print(f"集合 B: {set_b}")
    print(f"差集 A - B (使用 difference()): {difference_ab_method}")
    print(f"差集 A - B (使用 - 运算符): {difference_ab_operator}")
    print(f"差集 B - A (作为对比): {difference_ba}")
    print(f"A - B 等于 B - A 吗? {difference_ab_method == difference_ba}")
    return difference_ab_method

求对称差集(symmetric difference),找出'你我独家,非共同'的元素:

def perform_set_symmetric_difference(set_a: set, set_b: set) -> set:
    """ 计算并返回两个集合的对称差集。
    对称差集包含所有属于 set_a 或 set_b,但不同时属于两者的元素。
    可以理解为 (A | B) - (A & B)。
    参数:
        set_a (set): 第一个集合。
        set_b (set): 第二个集合。
    返回:
        set: set_a 和 set_b 的对称差集。
    """
    # 方法 1: 使用 symmetric_difference() 方法
    sym_diff_by_method = set_a.symmetric_difference(set_b)
    # 方法 2: 使用 ^ 运算符
    sym_diff_by_operator = set_a ^ set_b
    # 通过并集和交集验证
    union_set = set_a | set_b
    inter_set = set_a & set_b
    verification = union_set - inter_set
    print(f"集合 A: {set_a}")
    print(f"集合 B: {set_b}")
    print(f"对称差集 (使用 symmetric_difference()): {sym_diff_by_method}")
    print(f"对称差集 (使用 ^ 运算符): {sym_diff_by_operator}")
    print(f"验证 (并集 - 交集): {verification}")
    print(f"结果一致吗? {sym_diff_by_method == verification}")
    return sym_diff_by_method

我们还可以分析两个集合之间的关系,比如一个集合是否完全包含另一个,或者它们是否毫无交集。

def check_set_relations(set_a: set, set_b: set) -> dict:
    """ 全面检查两个集合之间的所有可能关系。
    这对于理解数据间的包含和排斥关系非常有用。
    参数:
        set_a (set): 第一个集合。
        set_b (set): 第二个集合。
    返回:
        dict: 一个包含所有关系检查结果的字典。
    """
    results = {
        "A 是 B 的子集 (A ⊆ B)": set_a.issubset(set_b),
        "A 是 B 的真子集 (A ⊂ B)": set_a.issubset(set_b) and set_a != set_b,
        "A 是 B 的超集 (A ⊇ B)": set_a.issuperset(set_b),
        "A 是 B 的真超集 (A ⊃ B)": set_a.issuperset(set_b) and set_a != set_b,
        "A 和 B 相等 (A == B)": set_a == set_b,
        "A 和 B 不相交 (无共同元素)": set_a.isdisjoint(set_b),
    }
    print(f"集合 A: {set_a}")
    print(f"集合 B: {set_b}")
    print("\n关系检查结果:")
    for relation, result in results.items():
        print(f" {relation}: {result}")
    return results

到这里,我们讨论的都是普通的 set,它是可变的,创建后可以随意增删元素。但 Python 还提供了一个不可变的版本——frozenset(冻结集合)。正如其名,一旦创建,内容就无法更改。这带来了一个关键优势:frozenset 是可哈希的。

这意味着什么呢?一个对象如果是可哈希的(比如数字、字符串、元组),它就可以作为字典的键(key),或者被放入另一个集合中。而普通的可变集合做不到这一点。

def create_and_use_frozenset(iterable_data):
    """ 创建并使用冻结集合 (frozenset)。
    冻结集合是不可变的集合,一旦创建,就不能添加、删除或修改其元素。
    它可以用作字典的键或其他集合的元素。
    参数:
        iterable_data: 可迭代对象,如列表、元组、字符串或另一个集合。
    返回:
        frozenset: 新创建的冻结集合。
    """
    # 1. 创建冻结集合
    frozen = frozenset(iterable_data)
    print(f"创建的冻结集合:{frozen}")
    print(f"类型:{type(frozen)}")
    # 2. 演示支持的操作(读取/计算)
    print("\n冻结集合支持的操作(不修改自身):")
    print(f" 长度:{len(frozen)}")
    print(f" 成员检查 ('a' in frozen): {'a' in frozen}")
    if len(frozen) > 0:
        sample_element = next(iter(frozen))
        print(f" 迭代(第一个元素): {sample_element}")
    # 3. 演示集合运算(返回新集合)
    another_set = {1, 2, 3}
    print(f"\n与普通集合 {{1, 2, 3}} 进行运算:")
    print(f" 并集 (frozen | another_set): {frozen | another_set}")
    print(f" 交集 (frozen & another_set): {frozen & another_set}")
    # 4. 尝试修改操作(将引发错误)
    print(f"\n尝试修改操作(将导致 AttributeError):")
    try:
        # 以下操作都会失败
        # frozen.add(99) # 取消注释会报错
        print(" frozen.add(99): 不可用")
    except AttributeError as e:
        print(f" 错误:{type(e).__name__}: {e}")
    return frozen

让我们看看冻结集合作为字典键的实际应用:

def demonstrate_frozenset_as_key():
    """ 演示冻结集合的核心用途之一:作为字典的键。
    因为冻结集合是不可变的、可哈希的,所以它可以作为字典的键,
    而普通的可变集合(set)则不行。
    """
    # 创建几个冻结集合作为'键'
    key1 = frozenset([1, 2, 3])
    key2 = frozenset(['a', 'b'])
    key3 = frozenset(['x', 'y', 'z'])
    print("创建的冻结集合键:")
    print(f" key1: {key1}")
    print(f" key2: {key2}")
    print(f" key3: {key3}")
    # 创建一个字典,使用冻结集合作为键
    my_dict = {
        key1: "与数字集合相关的值",
        key2: "与字母集合相关的值",
        key3: "另一个字母集合的值"
    }
    print("\n使用冻结集合作为键的字典:")
    for key, value in my_dict.items():
        print(f" {key} -> {value}")
    # 正常通过键访问值
    lookup_key = frozenset([1, 2, 3])
    print(f"\n查询键 {lookup_key}:")
    if lookup_key in my_dict:
        print(f" 找到的值:'{my_dict[lookup_key]}'")
    else:
        print(" 未找到该键。")
    # 对比:尝试用普通集合作为键(会引发 TypeError)
    print("\n尝试使用普通集合作为字典键(将失败):")
    try:
        bad_dict = {set([1, 2]): "这将失败"}
    except TypeError as e:
        print(f" 预期错误:{type(e).__name__}: {e} - 因为集合是不可哈希的。")

正因为冻结集合可哈希,我们才能实现集合的嵌套,例如创建一个包含多个集合的'大集合':

def create_set_of_frozensets(list_of_lists: list[list]) -> set:
    """ 创建一个集合,其元素是多个冻结集合。
    由于冻结集合是不可变的、可哈希的,它们可以作为另一个集合的元素。
    这是存储一组唯一'集合组'的有效方式。
    参数:
        list_of_lists (list[list]): 一个列表,其中每个元素是一个列表(将转换为冻结集合)。
    返回:
        set: 一个包含冻结集合的集合。
    """
    set_of_frozen = set()
    print("从以下列表创建冻结集合并加入一个总集合中:")
    for i, sublist in enumerate(list_of_lists):
        # 将每个子列表转换为冻结集合
        frozen_elem = frozenset(sublist)
        print(f" 列表{i+1}: {sublist} -> 冻结集合:{frozen_elem}")
        # 将冻结集合添加到总集合中
        set_of_frozen.add(frozen_elem)
    print(f"\n最终创建的'冻结集合的集合':")
    print(f" {set_of_frozen}")
    print(f" 类型:{type(set_of_frozen)}")
    print(f" 大小:{len(set_of_frozen)}")
    # 演示重复的冻结集合不会再次添加(因为集合的唯一性)
    print("\n尝试添加一个已存在的冻结集合(内容与列表 1 相同)...")
    duplicate_frozen = frozenset(list_of_lists[0])
    before_size = len(set_of_frozen)
    set_of_frozen.add(duplicate_frozen)
    after_size = len(set_of_frozen)
    print(f" 添加前集合大小:{before_size}")
    print(f" 添加后集合大小:{after_size}")
    print(f" 是否成功添加? {before_size != after_size} (False 表示因重复而被忽略)")
    return set_of_frozen

了解了核心特性与操作,集合在实际编程中能解决哪些具体问题呢?一个最经典的应用就是快速去重。相比手动遍历列表,使用集合几乎是一步到位,不过要留意,结果可能会打乱原列表的顺序。

另一个常见场景是多数据源对比。比如,从几个不同的渠道收集用户邮箱,想找出所有渠道都存在的用户,集合的交集运算能瞬间给出答案。

def find_common_elements_in_multiple_lists(*lists: list) -> set:
    """ 在多个列表中查找共同元素(存在于所有列表中的元素)。
    利用集合的 `intersection` 操作,可以非常高效地解决此问题。
    这是数据分析和处理中的常见任务。
    参数:
        *lists (list): 可变数量的列表参数。
    返回:
        set: 包含所有输入列表共有元素的集合。如果输入列表少于 2 个,返回空集。
    """
    if len(lists) < 2:
        print("警告:至少需要提供两个列表进行比较。")
        return set()
    print(f"在 {len(lists)} 个列表中查找共同元素:")
    for i, lst in enumerate(lists):
        print(f" 列表{i+1}: {lst}")
    # 核心算法:
    # 1. 将第一个列表转换为集合,作为初始交集结果。
    # 2. 依次与后续每个列表的集合进行交集运算。
    common_elements = set(lists[0])
    for lst in lists[1:]:
        common_elements = common_elements.intersection(lst)
        # 如果中途交集已为空,可以提前终止循环
        if not common_elements:
            break
    print(f"\n所有列表共有的元素是:")
    if common_elements:
        print(f" {common_elements}")
    else:
        print(" (没有共同元素)")
    return common_elements

在文本处理中,集合的高效成员检查特性(时间复杂度接近 O(1))大放异彩。比如过滤一段文本中的停用词(如'的'、'了'、'和'),用集合来存储停用词会比用列表快得多。

def filter_text_with_stopwords(text: str, stopwords_set: set) -> list:
    """ 使用停用词集合过滤一段文本。
    停用词(如'the', 'is', 'at')通常对文本分析意义不大,需要被移除。
    利用集合的 O(1) 平均时间复杂度成员检查,此操作非常高效。
    参数:
        text (str): 待处理的原始文本字符串。
        stopwords_set (set): 停用词集合。
    返回:
        list: 清理后(不含停用词)的单词列表。
    """
    # 预处理:转为小写,并按空格分割单词
    words = text.lower().split()
    original_word_count = len(words)
    print(f"原始文本:\"{text}\"")
    print(f"原始单词列表 ({original_word_count} 个): {words}")
    print(f"停用词集合 ({len(stopwords_set)} 个): {stopwords_set}")
    # 核心过滤:使用列表推导式,只保留不在停用词集合中的单词
    filtered_words = [word for word in words if word not in stopwords_set]
    filtered_word_count = len(filtered_words)
    print(f"\n过滤后的单词列表 ({filtered_word_count} 个): {filtered_words}")
    removed_count = original_word_count - filtered_word_count
    print(f"移除了 {removed_count} 个停用词。")
    return filtered_words

最后,让我们把这些技巧综合起来,模拟一个更复杂的场景:分析社交网络中不同人群的兴趣爱好,计算他们的共同点与独特点。

def analyze_social_network_interests(people_interests: dict) -> dict:
    """ 分析一组人的兴趣,使用集合运算发现各种关系。
    输入是一个字典,键是人名,值是该人兴趣的集合。
    此函数展示了集合在关系数据分析中的强大作用。
    参数:
        people_interests (dict): 格式为 {人名:兴趣集合} 的字典。
    返回:
        dict: 包含各种分析结果的字典,如共同兴趣、每个人的独家兴趣等。
    """
    print("分析社交网络兴趣...")
    # 此处省略具体实现细节,重点在于展示集合运算的综合应用
    return {}

这个函数就运用了并集、交集、差集等多种集合运算,清晰地呈现出数据间的关系。掌握集合,就是掌握了一种处理'唯一性'和'关系比较'问题的利器。

总结

集合将数学中的集合论思想带入了编程,提供了一种基于元素'存在与否'而非'顺序位置'的思考方式。当你需要处理唯一性项目、进行快速比较或筛选时,它就是最趁手的工具。而冻结集合则弥补了可变集合在需要不可变、可哈希场景下的空缺。

目录

  1. 第 4 章:无序且唯一的集合:集合与冻结集合
  2. 章节介绍
  3. 总结

更多推荐文章

查看全部
  • Mosquitto 安装与配置教程:支持 JavaScript 网页 WebSocket 连接
  • Linux 环境下 C/C++ 编译流程详解
  • Three.js 与 Cesium 整合技术概览
  • VSCode Copilot 接入智谱 GLM-5.1 大模型配置指南
  • RAG 应用程序中的意图路由策略与实践
  • 用Python免费抓取股票Tick级实时行情:AllTick WebSocket实战
  • JavaScript Promise 完整指南
  • 突破 LLM 上下文瓶颈:上下文内存虚拟化 CMV 的设计与实践
  • 无线蜂窝网络:从原理到 6G 演进
  • LLaMA-Factory 全流程模型训练与推理
  • 实用 AI 写作平台推荐:涵盖日常、论文及职场场景
  • Spring Security 自定义 UserDetailsService 实现用户认证
  • CPP Summit 2020 学习笔记:系统架构与设计
  • VectorBT:基于向量化运算的 Python 量化回测框架详解
  • 大模型在研发运营领域的应用
  • Cursor 2.2 更新:可视化编辑器与 Debug Mode 深度解析
  • 利用 Qoder 自然语言生成动漫短剧的 AIGC 智能体实战
  • 用 Self-Instruct 生成微调数据,让开源模型学会写测试用例
  • Java CountDownLatch 核心原理、API 详解与实战应用
  • Spring Boot 虚拟线程时代:WebFlux 与 WebMVC 的选型抉择

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online