# Python 字典与集合

## 学习目标
- 掌握字典的创建、访问、增删改查操作
- 理解集合的特性和数学运算
- 学会字典推导式和集合推导式
- 了解哈希表的基本原理

---

## 一、字典（dict）

字典是 Python 中用于存储键值对的数据结构，它是**无序**的（Python 3.7+ 实际保持插入顺序）、**键唯一**的映射类型。

### 1.1 创建字典

```python
# 直接创建
user = {"name": "Alice", "age": 25, "city": "Beijing"}

# 使用 dict() 构造函数
user2 = dict(name="Bob", age=30, city="Shanghai")

# 从键值对序列创建
user3 = dict([("name", "Charlie"), ("age", 35)])

# 空字典
empty = {}
empty2 = dict()

# 使用 fromkeys() 创建带默认值的字典
keys = ["a", "b", "c"]
d = dict.fromkeys(keys, 0)
print(d)   # {'a': 0, 'b': 0, 'c': 0}
```

### 1.2 访问和修改

```python
user = {"name": "Alice", "age": 25, "city": "Beijing"}

# 访问
print(user["name"])        # Alice
# print(user["gender"])    # KeyError!

# 安全访问
print(user.get("gender"))           # None
print(user.get("gender", "未知"))   # 未知（默认值）

# 修改和添加
user["age"] = 26           # 修改已有键
user["gender"] = "female"  # 添加新键
print(user)

# 批量更新
user.update({"phone": "123456", "email": "alice@example.com"})
print(user)

# 使用 setdefault：键不存在时设置默认值
user.setdefault("country", "China")
print(user["country"])     # China
user.setdefault("country", "USA")   # 已存在，不修改
print(user["country"])     # China
```

### 1.3 删除操作

```python
user = {"name": "Alice", "age": 25, "city": "Beijing"}

# 删除指定键
del user["city"]
print(user)                # {'name': 'Alice', 'age': 25}

# pop() 删除并返回值
age = user.pop("age")
print(age, user)           # 25 {'name': 'Alice'}

# popitem() 删除并返回最后一个键值对（Python 3.7+ 按插入顺序）
last = user.popitem()
print(last)                # ('name', 'Alice')

# clear() 清空
user.clear()
print(user)                # {}
```

### 1.4 遍历字典

```python
user = {"name": "Alice", "age": 25, "city": "Beijing"}

# 遍历键
for key in user:
    print(key)

# 遍历值
for value in user.values():
    print(value)

# 遍历键值对
for key, value in user.items():
    print(f"{key}: {value}")

# 检查键是否存在
print("name" in user)      # True
print("gender" in user)    # False
```

---

## 二、字典的高级用法

### 2.1 嵌套字典

```python
school = {
    "class_a": {
        "teacher": "Mr. Wang",
        "students": ["Alice", "Bob"]
    },
    "class_b": {
        "teacher": "Ms. Li",
        "students": ["Charlie", "David"]
    }
}

print(school["class_a"]["teacher"])       # Mr. Wang
print(school["class_b"]["students"][0])   # Charlie
```

### 2.2 字典合并（Python 3.9+）

```python
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}

# | 合并（3.9+）
merged = d1 | d2
print(merged)              # {'a': 1, 'b': 3, 'c': 4}

# |= 就地合并
d1 |= d2
print(d1)                  # {'a': 1, 'b': 3, 'c': 4}
```

### 2.3 字典排序

```python
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}

# 按键排序
sorted_by_key = dict(sorted(scores.items()))
print(sorted_by_key)       # {'Alice': 85, 'Bob': 92, 'Charlie': 78}

# 按值排序
sorted_by_value = dict(sorted(scores.items(), key=lambda x: x[1], reverse=True))
print(sorted_by_value)     # {'Bob': 92, 'Alice': 85, 'Charlie': 78}
```

---

## 三、集合（set）

集合是一个**无序、不重复**的元素集合，支持数学上的集合运算。

### 3.1 创建集合

```python
# 直接创建
s = {1, 2, 3, 3, 3}        # 重复自动去重
print(s)                   # {1, 2, 3}

# 使用 set() 构造函数
s2 = set([1, 2, 3, 3, 3])  # 从列表创建
print(s2)                  # {1, 2, 3}

# 空集合（注意：{} 是空字典！）
empty_set = set()
print(type(empty_set))     # <class 'set'>
```

### 3.2 集合操作

```python
s = {1, 2, 3}

# 添加元素
s.add(4)
print(s)                   # {1, 2, 3, 4}

# 批量添加
s.update([5, 6])
print(s)                   # {1, 2, 3, 4, 5, 6}

# 删除元素
s.remove(6)                # 元素不存在会报错
s.discard(100)             # 元素不存在不报错
popped = s.pop()           # 随机删除并返回一个元素
print(s)

s.clear()                  # 清空
```

### 3.3 集合运算

```python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# 并集
print(a | b)               # {1, 2, 3, 4, 5, 6}
print(a.union(b))

# 交集
print(a & b)               # {3, 4}
print(a.intersection(b))

# 差集
print(a - b)               # {1, 2}
print(a.difference(b))

# 对称差集（只在其中一个集合中的元素）
print(a ^ b)               # {1, 2, 5, 6}
print(a.symmetric_difference(b))

# 子集和超集
print({1, 2} < a)          # True（真子集）
print({1, 2, 3} <= a)      # True（子集）
print(a > {1, 2})          # True（真超集）
```

### 3.4 不可变集合 frozenset

```python
# frozenset 不可变，可作为字典的键
fs = frozenset([1, 2, 3])
# fs.add(4)                # AttributeError!

# 可用作字典键
mapping = {frozenset([1, 2]): "A", frozenset([3, 4]): "B"}
print(mapping[frozenset([1, 2])])   # A
```

---

## 四、推导式

### 4.1 字典推导式

```python
# 基本字典推导式
squares = {x: x**2 for x in range(6)}
print(squares)             # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 带条件
evens = {x: x**2 for x in range(10) if x % 2 == 0}
print(evens)               # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

# 交换键值
original = {"a": 1, "b": 2, "c": 3}
swapped = {v: k for k, v in original.items()}
print(swapped)             # {1: 'a', 2: 'b', 3: 'c'}

# 从两个列表创建字典
keys = ["a", "b", "c"]
values = [1, 2, 3]
d = {k: v for k, v in zip(keys, values)}
print(d)                   # {'a': 1, 'b': 2, 'c': 3}
```

### 4.2 集合推导式

```python
# 基本集合推导式
s = {x**2 for x in range(10)}
print(s)                   # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

# 带条件
evens = {x for x in range(20) if x % 2 == 0}
print(evens)               # {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}

# 去重（从列表提取不重复元素）
items = [1, 2, 2, 3, 3, 3, 4]
unique = {x for x in items}
print(unique)              # {1, 2, 3, 4}
```

---

## 五、哈希原理简述

字典和集合的键必须是**可哈希（hashable）**的对象。可哈希对象的特点：

- 不可变（immutable）
- 有固定的哈希值（通过 `hash()` 函数计算）

```python
# 可哈希的对象（可做字典键）
print(hash("hello"))       # 字符串可哈希
print(hash(42))            # 整数可哈希
print(hash((1, 2, 3)))     # 元组可哈希

# 不可哈希的对象（不能做字典键）
# hash([1, 2, 3])          # TypeError: 列表不可哈希
# hash({"a": 1})           # TypeError: 字典不可哈希

# 检查是否可哈希
from collections.abc import Hashable
print(isinstance("hello", Hashable))   # True
print(isinstance([1, 2], Hashable))    # False
```

---

## 六、综合示例

```python
"""
词频统计程序
"""

text = """
Python is great and Python is easy.
Learning Python is fun and Python is powerful.
"""

# 清洗并分词
words = text.lower().replace(".", "").replace(",", "").split()

# 统计词频
frequency = {}
for word in words:
    frequency[word] = frequency.get(word, 0) + 1

# 使用字典推导式
# frequency = {word: words.count(word) for word in set(words)}

print("词频统计:")
print("-" * 30)
for word, count in sorted(frequency.items(), key=lambda x: x[1], reverse=True):
    print(f"{word:<12} {count:>3} 次")

# 找出只出现一次的单词
unique_words = {word for word, count in frequency.items() if count == 1}
print(f"\n只出现一次的单词: {unique_words}")

# 找出最常见的词
most_common = max(frequency.items(), key=lambda x: x[1])
print(f"最常见的词: '{most_common[0]}' 出现了 {most_common[1]} 次")
```

---

## 小结

- 字典存储键值对，键必须可哈希且唯一，值可以是任意类型
- `get()`、`setdefault()`、`update()` 是字典的常用方法
- 集合无序不重复，支持并集、交集、差集等数学运算
- 字典推导式和集合推导式语法与列表推导式类似
- `frozenset` 是不可变集合，可作为字典键
- 可哈希对象必须是不可变的（字符串、数字、元组等）

---

## 练习

1. 有一个字符串 `"hello world hello python"`，统计每个单词出现的次数。
2. 两个列表 `a = [1, 2, 3, 4, 5]` 和 `b = [4, 5, 6, 7, 8]`，找出它们的共同元素和各自独有的元素。
3. 编写程序，删除字典中值为 `None` 或空字符串的键值对。
4. 使用集合推导式从一段文本中提取所有不重复的字母（忽略大小写和标点）。
5. 有一个嵌套字典表示的菜单，编写函数按价格从低到高打印所有菜品。

