Python-6 字典与集合:哈希、键冲突与性能

专栏导读
  • 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手
  • 🏳️‍🌈 个人博客主页:请点击——> 个人的博客主页 求收藏
  • 🏳️‍🌈 Github主页:请点击——> Github主页 求Star⭐
  • 🏳️‍🌈 知乎主页:请点击——> 知乎主页 求关注
  • 🏳️‍🌈 CSDN博客主页:请点击——> CSDN的博客主页 求关注
  • 👍 该系列文章专栏:请点击——>Python办公自动化专栏 求订阅
  • 🕷 此外还有爬虫专栏:请点击——>Python爬虫基础专栏 求订阅
  • 📕 此外还有python基础专栏:请点击——>Python基础学习专栏 求订阅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • ❤️ 欢迎各位佬关注! ❤️

前言

字典(dict)和集合(set)是 Python 中基于哈希表实现的两种重要数据结构。它们提供了快速的查找、插入和删除操作,是构建高效 Python 程序的关键工具。本文将深入探讨它们的内部机制、性能特性和使用技巧。

字典:键值对的魔法

字典基础

字典是 Python 中最重要的数据结构之一,用于存储键值对:

# 创建字典的多种方式
empty_dict = {}
person = {"name": "张三", "age": 25, "city": "北京"}
numbers = dict(one=1, two=2, three=3)
pairs = dict([("a", 1), ("b", 2), ("c", 3)])
zip_dict = dict(zip(["x", "y", "z"], [10, 20, 30]))

print(f"空字典: {empty_dict}")
print(f"人员信息: {person}")
print(f"数字: {numbers}")
print(f"键值对: {pairs}")
print(f"zip创建: {zip_dict}")

# 字典推导式
squares = {x: x**2 for x in range(5)}
print(f"平方数: {squares}")

字典的基本操作

# 访问和修改
student = {"name": "李四", "age": 20, "grades": [85, 90, 88]}

# 访问值
print(f"姓名: {student['name']}")
print(f"年龄: {student.get('age')}")
print(f"地址: {student.get('address', '未知')}")  # 使用默认值

# 修改值
student["age"] = 21
student["grades"].append(92)
print(f"更新后: {student}")

# 添加新键值对
student["email"] = "lisi@example.com"
student.update({"phone": "13812345678", "major": "计算机科学"})
print(f"添加后: {student}")

# 删除键值对
del student["phone"]
email = student.pop("email")
print(f"删除后: {student}, 删除的邮箱: {email}")

字典的遍历

# 字典遍历的多种方式
scores = {"语文": 85, "数学": 92, "英语": 88, "物理": 90}

# 遍历键
for subject in scores:
    print(f"科目: {subject}")

# 遍历值
for score in scores.values():
    print(f"分数: {score}")

# 遍历键值对
for subject, score in scores.items():
    print(f"{subject}: {score}分")

# 带索引的遍历
for i, (subject, score) in enumerate(scores.items()):
    print(f"{i+1}. {subject}: {score}分")

# 计算平均分
average = sum(scores.values()) / len(scores)
print(f"平均分: {average:.2f}")

集合:唯一值的集合

集合基础

集合是存储唯一元素的无序集合:

# 创建集合
empty_set = set()
numbers = {1, 2, 3, 4, 5}
string_set = set("hello")  # {'h', 'e', 'l', 'o'}
list_set = set([1, 2, 2, 3, 3, 4])  # {1, 2, 3, 4}

print(f"空集合: {empty_set}")
print(f"数字集合: {numbers}")
print(f"字符串集合: {string_set}")
print(f"列表转集合: {list_set}")

# 集合推导式
even_squares = {x**2 for x in range(10) if x % 2 == 0}
print(f"偶数平方: {even_squares}")

集合的操作

# 基本集合操作
set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}

# 添加和删除元素
set_a.add(6)
set_a.discard(2)  # 安全删除(不存在不报错)
set_a.remove(3)   # 删除(不存在会报错)
popped = set_a.pop()  # 随机删除并返回一个元素
print(f"操作后: {set_a}, 弹出的元素: {popped}")

# 集合运算
union = set_a | set_b  # 并集
intersection = set_a & set_b  # 交集
difference = set_a - set_b  # 差集
symmetric_diff = set_a ^ set_b  # 对称差集

print(f"A: {set_a}")
print(f"B: {set_b}")
print(f"并集: {union}")
print(f"交集: {intersection}")
print(f"差集: {difference}")
print(f"对称差集: {symmetric_diff}")

# 集合关系
subset = {1, 2}.issubset({1, 2, 3})
superset = {1, 2, 3}.issuperset({1, 2})
disjoint = {1, 2}.isdisjoint({3, 4})

print(f"子集: {subset}")
print(f"超集: {superset}")
print(f"不相交: {disjoint}")

哈希表的内部机制

哈希原理

字典和集合的高效性来自于哈希表的实现:

# 查看对象的哈希值
print(f"整数 42 的哈希: {hash(42)}")
print(f"浮点数 3.14 的哈希: {hash(3.14)}")
print(f"字符串 'hello' 的哈希: {hash('hello')}")

# 不可变对象才有哈希值
try:
    print(f"列表的哈希: {hash([1, 2, 3])}")
except TypeError as e:
    print(f"列表没有哈希值: {e}")

# 元组有哈希值(如果元素都是不可变的)
tuple_hash = hash((1, 2, 3))
print(f"元组的哈希: {tuple_hash}")

# 嵌套元组
try:
    nested_hash = hash((1, [2, 3]))
except TypeError as e:
    print(f"包含列表的元组没有哈希值: {e}")

哈希冲突处理

Python 使用开放寻址法处理哈希冲突:

# 演示哈希冲突的概念
class SimpleHashTable:
    """简单的哈希表实现,演示冲突处理"""
    
    def __init__(self, size=8):
        self.size = size
        self.table = [None] * size
        self.count = 0
    
    def _hash(self, key):
        """简单的哈希函数"""
        return hash(key) % self.size
    
    def _probe(self, index, i):
        """线性探测"""
        return (index + i) % self.size
    
    def insert(self, key, value):
        """插入键值对"""
        if self.count >= self.size * 0.7:  # 负载因子超过0.7就扩容
            self._resize()
        
        index = self._hash(key)
        i = 0
        
        while self.table[index] is not None and self.table[index][0] != key:
            i += 1
            index = self._probe(index, i)
            
            if i >= self.size:  # 避免无限循环
                raise RuntimeError("哈希表已满")
        
        if self.table[index] is None:
            self.count += 1
        
        self.table[index] = (key, value)
    
    def get(self, key):
        """获取值"""
        index = self._hash(key)
        i = 0
        
        while self.table[index] is not None:
            if self.table[index][0] == key:
                return self.table[index][1]
            i += 1
            index = self._probe(index, i)
            
            if i >= self.size:
                break
        
        raise KeyError(f"键 {key} 不存在")
    
    def _resize(self):
        """扩容"""
        old_table = self.table
        self.size *= 2
        self.table = [None] * self.size
        self.count = 0
        
        for item in old_table:
            if item is not None:
                self.insert(item[0], item[1])
    
    def display(self):
        """显示哈希表内容"""
        print("哈希表内容:")
        for i, item in enumerate(self.table):
            if item is not None:
                print(f"  槽位 {i}: {item[0]} -> {item[1]}")
            else:
                print(f"  槽位 {i}: 空")

# 测试简单哈希表
hash_table = SimpleHashTable(size=8)
hash_table.insert("name", "张三")
hash_table.insert("age", 25)
hash_table.insert("city", "北京")
hash_table.insert("job", "程序员")

hash_table.display()

print(f"获取 name: {hash_table.get('name')}")
print(f"获取 age: {hash_table.get('age')}")

字典的高级用法

1. 默认值处理

# 使用 setdefault() 处理默认值
word_count = {}
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]

for word in words:
    word_count.setdefault(word, 0)
    word_count[word] += 1

print(f"单词计数: {word_count}")

# 使用 defaultdict(需要导入 collections)
from collections import defaultdict

word_count2 = defaultdict(int)  # 默认值为 0
for word in words:
    word_count2[word] += 1  # 自动初始化为 0

print(f"defaultdict 计数: {dict(word_count2)}")

# 嵌套的 defaultdict
def nested_defaultdict():
    return defaultdict(list)

nested = defaultdict(nested_defaultdict)
nested["fruits"]["sweet"].append("apple")
nested["fruits"]["sweet"].append("banana")
nested["vegetables"]["green"].append("spinach")

print(f"嵌套结构: {dict(nested)}")

2. 字典合并

# Python 3.9+ 的合并操作符
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}

# 合并(后面的字典优先级高)
merged = dict1 | dict2
print(f"合并结果: {merged}")  # {'a': 1, 'b': 3, 'c': 4}

# 原地合并
dict1 |= dict2
print(f"原地合并: {dict1}")

# 早期版本的合并方法
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}

# 方法1:使用 update()
merged1 = dict1.copy()
merged1.update(dict2)
print(f"update 合并: {merged1}")

# 方法2:使用解包操作符
merged2 = {**dict1, **dict2}
print(f"解包合并: {merged2}")

# 方法3:使用 ChainMap(保留多个字典的引用)
from collections import ChainMap
chain = ChainMap(dict2, dict1)
print(f"ChainMap: {dict(chain)}")

3. 字典排序

# 按值排序
scores = {"张三": 85, "李四": 92, "王五": 78, "赵六": 88}

# 按分数降序排序
sorted_by_score = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(f"按分数排序: {sorted_by_score}")

# 按键排序
sorted_by_name = dict(sorted(scores.items()))
print(f"按姓名排序: {sorted_by_name}")

# 多层排序
students = [
    {"name": "张三", "class": "A", "score": 85},
    {"name": "李四", "class": "B", "score": 92},
    {"name": "王五", "class": "A", "score": 88},
    {"name": "赵六", "class": "B", "score": 78}
]

# 先按班级,再按分数排序
sorted_students = sorted(students, key=lambda x: (x["class"], -x["score"]))
print("多层排序结果:")
for student in sorted_students:
    print(f"  {student['class']}{student['name']}: {student['score']}分")

集合的高级用法

1. 集合推导式

# 基本集合推导式
even_squares = {x**2 for x in range(10) if x % 2 == 0}
print(f"偶数平方: {even_squares}")

# 从字符串创建唯一字符
unique_chars = {char for char in "hello world" if char.isalpha()}
print(f"唯一字符: {unique_chars}")

# 复杂的集合推导式
import math
prime_candidates = {x for x in range(2, 20) 
                   if all(x % i != 0 for i in range(2, int(math.sqrt(x)) + 1))}
print(f"质数候选: {prime_candidates}")

2. 冻结集合(frozenset)

# 创建 frozenset
frozen = frozenset([1, 2, 3, 2, 1])
print(f"冻结集合: {frozen}")

# frozenset 是不可变的,可以作为字典的键
course_prerequisites = {
    frozenset(["数学", "物理"]): "高等数学",
    frozenset(["编程", "算法"]): "数据结构",
    frozenset(["英语"]): "基础英语"
}

# 查找课程
math_physics = frozenset(["数学", "物理"])
print(f"数学+物理的先修课程: {course_prerequisites[math_physics]}")

# frozenset 支持集合运算
set1 = frozenset([1, 2, 3])
set2 = frozenset([3, 4, 5])
union = set1 | set2
intersection = set1 & set2
print(f"并集: {union}")
print(f"交集: {intersection}")

3. 集合的实际应用

# 1. 标签系统
class TagSystem:
    def __init__(self):
        self.items = {}  # 物品到标签的映射
        self.tags = {}   # 标签到物品的映射
    
    def add_item(self, item, tags):
        """添加物品和标签"""
        self.items[item] = set(tags)
        for tag in tags:
            if tag not in self.tags:
                self.tags[tag] = set()
            self.tags[tag].add(item)
    
    def find_items_by_tags(self, required_tags, optional_tags=None):
        """根据标签查找物品"""
        if not required_tags:
            return set()
        
        required_set = set(required_tags)
        
        # 找到包含所有必需标签的物品
        result = None
        for tag in required_set:
            if tag in self.tags:
                if result is None:
                    result = self.tags[tag].copy()
                else:
                    result &= self.tags[tag]
            else:
                return set()  # 有必需标签不存在
        
        # 如果有可选标签,按匹配数量排序
        if optional_tags and result:
            optional_set = set(optional_tags)
            scored_items = []
            for item in result:
                item_tags = self.items[item]
                optional_matches = len(item_tags & optional_set)
                scored_items.append((optional_matches, item))
            
            # 按匹配数量降序排序
            scored_items.sort(reverse=True)
            return [item for _, item in scored_items]
        
        return result if result else set()
    
    def get_similar_items(self, item, limit=5):
        """找到相似物品(基于标签重叠)"""
        if item not in self.items:
            return []
        
        item_tags = self.items[item]
        similarities = []
        
        for other_item, other_tags in self.items.items():
            if other_item != item:
                common_tags = len(item_tags & other_tags)
                total_tags = len(item_tags | other_tags)
                similarity = common_tags / total_tags if total_tags > 0 else 0
                similarities.append((similarity, other_item))
        
        # 按相似度降序排序
        similarities.sort(reverse=True)
        return [item for _, item in similarities[:limit]]

# 测试标签系统
tag_system = TagSystem()

# 添加一些物品
tag_system.add_item("Python编程书", ["编程", "Python", "入门", "书籍"])
tag_system.add_item("数据科学课程", ["编程", "Python", "数据科学", "在线课程"])
tag_system.add_item("JavaScript教程", ["编程", "JavaScript", "前端", "入门"])
tag_system.add_item("机器学习书籍", ["编程", "Python", "机器学习", "书籍", "进阶"])
tag_system.add_item("摄影入门", ["摄影", "入门", "艺术"])

# 搜索物品
print("搜索包含'编程'和'Python'的物品:")
results = tag_system.find_items_by_tags(["编程", "Python"])
for item in results:
    print(f"  {item}")

print("\n搜索包含'编程',最好还有'入门'的物品:")
results = tag_system.find_items_by_tags(["编程"], ["入门"])
for item in results:
    print(f"  {item}")

print("\n与'Python编程书'相似的物品:")
similar = tag_system.get_similar_items("Python编程书")
for item in similar:
    print(f"  {item}")

性能分析和优化

1. 字典性能测试

import time
import random
import string

def performance_test():
    """字典性能测试"""
    
    # 生成测试数据
    def generate_random_string(length=10):
        return ''.join(random.choices(string.ascii_lowercase, k=length))
    
    # 测试不同大小的字典
    sizes = [100, 1000, 10000]
    
    for size in sizes:
        print(f"\n测试字典大小: {size}")
        
        # 创建字典
        test_dict = {generate_random_string(): i for i in range(size)}
        test_keys = list(test_dict.keys())
        
        # 测试查找性能
        start_time = time.time()
        for _ in range(1000):
            key = random.choice(test_keys)
            value = test_dict[key]
        lookup_time = time.time() - start_time
        
        # 测试插入性能
        start_time = time.time()
        for i in range(1000):
            test_dict[generate_random_string()] = i + size
        insert_time = time.time() - start_time
        
        # 测试删除性能
        delete_keys = [generate_random_string() for _ in range(1000)]
        for key in delete_keys:
            test_dict[key] = 0  # 先插入
        
        start_time = time.time()
        for key in delete_keys:
            if key in test_dict:
                del test_dict[key]
        delete_time = time.time() - start_time
        
        print(f"查找1000次: {lookup_time:.4f}秒")
        print(f"插入1000次: {insert_time:.4f}秒")
        print(f"删除1000次: {delete_time:.4f}秒")

performance_test()

2. 内存使用优化

import sys
from collections import OrderedDict, defaultdict

def memory_comparison():
    """内存使用比较"""
    
    # 测试数据
    data = {f"key_{i}": f"value_{i}" for i in range(1000)}
    
    # 普通字典
    regular_dict = data.copy()
    
    # OrderedDict(Python 3.7+ 之前保持插入顺序)
    ordered_dict = OrderedDict(data)
    
    # defaultdict
    default_dict = defaultdict(str, data)
    
    # 计算内存使用
    regular_size = sys.getsizeof(regular_dict)
    for key, value in regular_dict.items():
        regular_size += sys.getsizeof(key) + sys.getsizeof(value)
    
    ordered_size = sys.getsizeof(ordered_dict)
    for key, value in ordered_dict.items():
        ordered_size += sys.getsizeof(key) + sys.getsizeof(value)
    
    default_size = sys.getsizeof(default_dict)
    for key, value in default_dict.items():
        default_size += sys.getsizeof(key) + sys.getsizeof(value)
    
    print(f"普通字典内存: {regular_size} 字节")
    print(f"OrderedDict内存: {ordered_size} 字节")
    print(f"defaultdict内存: {default_size} 字节")

memory_comparison()

常见陷阱和最佳实践

1. 字典键的可变性

# ⚠️ 错误:使用可变对象作为字典键
try:
    bad_dict = {[1, 2]: "value"}
except TypeError as e:
    print(f"错误: {e}")

# ✅ 正确:使用不可变对象作为键
# 使用元组代替列表
good_dict = {(1, 2): "value"}
print(f"元组作为键: {good_dict}")

# 使用字符串
string_dict = {"key_1_2": "value"}
print(f"字符串作为键: {string_dict}")

# 使用 frozenset
frozen_dict = {frozenset([1, 2]): "value"}
print(f"frozenset作为键: {frozen_dict}")

2. 集合的修改陷阱

# ⚠️ 错误:在遍历集合时修改它
numbers = {1, 2, 3, 4, 5}
try:
    for num in numbers:
        if num % 2 == 0:
            numbers.remove(num)  # 运行时错误
except RuntimeError as e:
    print(f"错误: {e}")

# ✅ 正确:先创建要删除的列表
to_remove = [num for num in numbers if num % 2 == 0]
for num in to_remove:
    numbers.remove(num)
print(f"删除偶数后: {numbers}")

# 或者使用集合推导式创建新集合
numbers = {1, 2, 3, 4, 5}
filtered = {num for num in numbers if num % 2 != 0}
print(f"过滤后: {filtered}")

3. 性能陷阱

# ⚠️ 低效的键存在检查
large_dict = {f"key_{i}": i for i in range(10000)}

# 低效的方法:遍历所有键
def slow_lookup(dictionary, key):
    for k in dictionary:
        if k == key:
            return dictionary[k]
    return None

# 高效的方法:直接使用键访问
def fast_lookup(dictionary, key):
    return dictionary.get(key)

import time

# 性能比较
key = "key_5000"

start = time.time()
for _ in range(1000):
    slow_result = slow_lookup(large_dict, key)
slow_time = time.time() - start

start = time.time()
for _ in range(1000):
    fast_result = fast_lookup(large_dict, key)
fast_time = time.time() - start

print(f"慢速查找: {slow_time:.4f}秒")
print(f"快速查找: {fast_time:.4f}秒")
print(f"加速比: {slow_time/fast_time:.0f}倍")

总结

字典和集合是 Python 中基于哈希表实现的强大数据结构:

  1. 字典提供了键值对的快速存储和检索,平均时间复杂度为 O(1)
  2. 集合提供了唯一元素的集合运算,支持并集、交集、差集等操作
  3. 哈希机制是这些数据结构高效性的基础,理解哈希冲突有助于编写更好的代码
  4. 性能优化方面,字典和集合的查找、插入、删除操作都非常高效
  5. 使用场景:字典适合存储结构化数据,集合适合处理唯一性和集合运算

掌握字典和集合的使用,能够让你编写出更加高效和优雅的 Python 代码。记住:

  • 使用不可变对象作为字典键
  • 利用集合进行唯一性检查和集合运算
  • 理解哈希原理有助于避免性能陷阱
  • 选择合适的数据结构对程序性能至关重要

在下一篇文章中,我们将探讨 Python 的控制流,学习如何使用条件语句、循环和各种控制结构来构建复杂的程序逻辑。

结尾
  • 希望对初学者有帮助;致力于办公自动化的小小程序员一枚
  • 希望能得到大家的【❤️一个免费关注❤️】感谢!
  • 求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍
  • 此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏
  • 此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏
  • 此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小庄-Python办公

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值