# 教案：第21节 —— 综合项目一（set + dict 综合创意课）

---

## 元信息

| 项目 | 内容 |
|------|------|
| **课程编号** | G3_第21节 |
| **GESP等级** | **三级**（GESP Python 三级考点） |
| **适用年龄** | 10-12 岁（算法入门阶段） |
| **核心知识点** | set 去重规范化、dict 颜色映射、集合推导式、反向映射、综合画图项目 |
| **记忆钩子** | "set 去重管颜色 dict 存映射 推导式一键搞定" |
| **身体锚点** | ① set 去重（筛选手） ② dict 映射（配对指） ③ 综合项目（全手掌画） |

---

## 课程目标

1. **熟练** 使用 set 进行数据去重、规范化清洗
2. **掌握** dict 的 get/setdefault 安全查询和构建反向映射
3. **运用** 集合推导式和字典推导式批量处理数据
4. **综合** set + dict 完成迷你画图程序

---

## 项目文件结构

| 编号 | 文件 | 难度 | 主题 |
|------|------|------|------|
| 00 | 课程概述.py | ☆ | 课程说明 |
| 01 | 挑战1_颜色去重与映射.py | ★ | set 去重 + dict 存映射 |
| 02 | 挑战2_画笔配置管理.py | ★★ | dict + set 管理画笔配置 |
| 03 | 挑战3_多色画板.py | ★★★ | 多色画板（综合应用） |
| 04-07 | 练习1-3 + 进阶 | ★-★★+ | 练习与进阶 |
| 08 | 综合项目_迷你画图程序.py | ★★★ | 完整迷你画图程序 |
| 09 | 总结与拓展.py | ☆ | 知识图谱 + 拓展方向 |

---

## 五段式 60 分钟教学流程

---

### 第一段：导入（00:00 - 10:00）—— "数据清洗大作战"

#### 教师活动

| 时间 | 活动内容 | 教学方式 |
|------|---------|---------|
| 0-3min | 拿出一些写有颜色的纸条（红、RED、 Red、蓝色、BLUE），问："这些颜色哪些是重复的？" | 实物互动 |
| 3-6min | "今天我们就要用 set + dict 来做一个'颜色管理系统'，还能控制 turtle 画画！" | 目标宣布 |
| 6-10min | 展示最终成果：运行迷你画图程序，用键盘快捷键切换颜色画图 | 成果预览 |

#### 导入问题

> "假如你从网上收集了一堆颜色名称，有 Red、red、 Red、BLUE、blue、Blue……"
> "怎么快速知道有几种不同的颜色？"
> "怎么把每种颜色和它的 RGB 值对应起来？"
> "这就是 set + dict 的经典组合应用！"

#### 身体锚点

> **【身体锚点 — 数据清洗流水线】**
> 左手做"筛子"动作（set 去重）：筛掉重复的。
> 右手做"配对"动作（dict 映射）：每种颜色配一个 RGB 值。
> 双手一起做"流水线"动作：先筛选，再配对，一气呵成！

---

### 第二段：知识点精讲（10:00 - 30:00）

#### 2.1 set 去重 + 规范化（6min）

**带脏数据的数据清洗：**

```python
raw_colors = [
    "Red", "red", "  Red ", "BLUE", "blue", "Blue",
    "GREEN", "Green", "green ", "YELLOW", "yellow",
    "purple", "PURPLE", "Purple", "  pink  ", "PINK", "pink",
]

# 集合推导式：去重 + 去掉空格 + 首字母大写
normalized = {color.strip().capitalize() for color in raw_colors}

print(f"原始数量: {len(raw_colors)}")
print(f"去重后: {len(normalized)}")
print(f"唯一颜色: {sorted(normalized)}")
```

**清洗三步骤：**
1. strip() —— 去掉首尾空格
2. capitalize() —— 首字母大写
3. set 推导式 —— 自动去重

#### 2.2 dict 颜色映射 + 安全查询（6min）

**建立颜色到 RGB 的映射：**

```python
color_rgb = {
    "Red":    (255, 0, 0),
    "Green":  (0, 255, 0),
    "Blue":   (0, 0, 255),
    "Yellow": (255, 255, 0),
    "Purple": (128, 0, 128),
    "Pink":   (255, 192, 203),
}

# 安全查询：dict.get()
for color in sorted(normalized):
    rgb = color_rgb.get(color)
    if rgb:
        print(f"{color:8s} -> RGB{rgb}")
    else:
        print(f"{color:8s} -> 未找到映射")
```

**dict 安全操作总结：**

| 方法 | 用途 | 示例 |
|------|------|------|
| dict.get(key) | 安全取值，不存在返回 None | color_rgb.get("Red") |
| dict.get(key, default) | 安全取值，指定默认值 | color_rgb.get("Gray", (128,128,128)) |
| dict.setdefault(key, default) | 不存在时设默认值并返回 | rgb_to_names.setdefault(rgb, []) |
| key in dict | 判断键是否存在 | "Red" in color_rgb |

#### 2.3 set 与 dict 结合：找缺失项（4min）

**检查 set 中有哪些颜色还没在 dict 中：**

```python
# set - dict.keys() 快速找出缺失项
missing_colors = normalized - set(color_rgb.keys())
if missing_colors:
    print(f"发现 {len(missing_colors)} 个颜色缺少映射")
    for name in sorted(missing_colors):
        # 自动生成占位 RGB
        h = hash(name) & 0xFFFFFF
        r, g, b = (h >> 16) & 0xFF, (h >> 8) & 0xFF, h & 0xFF
        color_rgb[name] = (r, g, b)
```

**关键技巧：** 集合相减 `set_a - set_b` 可以快速找出 A 中有 B 中没有的元素。

#### 2.4 反向映射构建（4min）

```python
# 反向映射：RGB -> 颜色名
rgb_to_names = {}
for name, rgb in color_rgb.items():
    rgb_to_names.setdefault(rgb, []).append(name)

for rgb, names in sorted(rgb_to_names.items(), key=lambda x: x[0]):
    print(f"RGB{rgb} -> {', '.join(names)}")

# 工具函数
def find_color_by_rgb(rgb):
    """根据 RGB 值查找所有匹配的颜色名称"""
    return rgb_to_names.get(rgb, [])
```

---

### 第三段：演示（30:00 - 40:00）

#### 演示 1：迷你画图程序结构

```python
import turtle

# dict 颜色调色板
COLOR_PALETTE = {
    "1": ("红色", "red"),
    "2": ("绿色", "green"),
    "3": ("蓝色", "blue"),
    "4": ("黄色", "yellow"),
    "5": ("紫色", "purple"),
    "6": ("青色", "cyan"),
    "7": ("橙色", "orange"),
}

# set 记录使用过的颜色
pen_history = set()

# dict 当前画笔配置
pen_config = {"color": "black", "size": 3, "shape": "line"}
```

#### 演示 2：键盘事件绑定切换颜色

```python
def change_color(key):
    """按数字键切换画笔颜色"""
    if key in COLOR_PALETTE:
        name, code = COLOR_PALETTE[key]
        pen.pencolor(code)
        pen_config["color"] = code
        pen_history.add(name)      # 记录使用过的颜色
        screen.title(f"当前颜色: {name}")

# 绑定键盘事件
screen.onkey(lambda: change_color("1"), "1")
screen.onkey(lambda: change_color("2"), "2")
# ... 绑定 3-7 键
```

#### 演示 3：统计信息展示

```python
def show_stats():
    """显示绘图统计信息"""
    info = f"使用过的颜色: {len(pen_history)} 种"
    info += f"\n当前画笔: {pen_config}"
    print(info)

    # 还可以显示更多信息
    print(f"颜色列表: {sorted(pen_history)}")
    print(f"调色板共 {len(COLOR_PALETTE)} 种颜色")
```

---

### 第四段：实操练习（40:00 - 55:00）

#### 练习 1：颜色数据清洗（5min）

```
题目：给定包含脏数据的颜色列表，用 set 推导式去重并规范化
```

**参考答案：**
```python
raw = ["  Red ", "red", "BLUE", " Blue", "blue", "RED"]
clean = {c.strip().capitalize() for c in raw}
print(clean)  # {'Red', 'Blue'}
```

#### 练习 2：建立颜色映射（5min）

```
题目：补全代码，建立至少 5 种颜色的颜色名到 RGB 的映射
      并用 get() 安全查询
```

**参考答案：**
```python
color_map = {
    "Red": (255, 0, 0),
    "Green": (0, 255, 0),
    "Blue": (0, 0, 255),
    "Yellow": (255, 255, 0),
    "Purple": (128, 0, 128),
}

name = input("输入颜色名: ")
rgb = color_map.get(name)
if rgb:
    print(f"{name} -> RGB{rgb}")
else:
    print("未找到该颜色")
```

#### 练习 3：综合练习——配置切换器（5min）

```
题目：用 dict 管理多组画笔配置，支持切换
```

**参考答案：**
```python
configs = {
    "细红笔": {"color": "red", "size": 1},
    "粗蓝笔": {"color": "blue", "size": 5},
    "绿荧光": {"color": "green", "size": 3},
}

choice = input("选择配置 (细红笔/粗蓝笔/绿荧光): ")
config = configs.get(choice)
if config:
    pen.pencolor(config["color"])
    pen.pensize(config["size"])
    print(f"已切换到: {choice}")
else:
    print("配置不存在")
```

---

### 第五段：小测（55:00 - 60:00）

#### 题目 1（选择）
集合推导式 `{x.strip() for x in items}` 的作用是？
A. 创建列表
B. 创建集合并自动去重
C. 创建字典
D. 创建元组

**答案：B**

#### 题目 2（选择）
dict.get(key) 在 key 不存在时返回什么？
A. 抛出 KeyError
B. 返回 None
C. 返回 0
D. 返回空字符串

**答案：B**

#### 题目 3（选择）
`set_a - set_b` 表示什么？
A. A 和 B 的并集
B. A 和 B 的交集
C. A 中有 B 中没有的元素
D. B 中有 A 中没有的元素

**答案：C**

#### 题目 4（判断）
dict.setdefault(key, default) 在 key 不存在时会设置默认值并返回它。

**答案：** 对

#### 题目 5（填空）
从 set 中找出不在 dict 中的键，可以用 `missing = set_data - ____`

**答案：** set(dict.keys())

---

## 板书设计

```
+-------------------------------------------------------+
|       第21节 综合项目一 set + dict                      |
+-------------------------------------------------------+

  数据清洗流水线：
  原始数据 -> strip() -> capitalize() -> set 去重

  dict 映射：
  color_rgb = {
      "Red": (255, 0, 0),
      "Green": (0, 255, 0),
      ...
  }

  安全操作三剑客：
  dict.get(key)           安全取值
  dict.setdefault(k, v)   设默认值
  key in dict            判断存在

  找缺失项：
  missing = set_data - set(dict.keys())

  记忆钩子：
  "set 去重管颜色 dict 存映射 推导式一键搞定"
---

## 课后延伸

| 项目 | 内容 |
|------|------|
| 课后练习 | 课后练习_第21节.md |
| 预习内容 | 第21节 综合项目二 |
| 拓展挑战 | 在 mini 画图程序中新增 5 种自定义颜色 |
| 家长沟通 | 今天学习了 set 和 dict 的组合应用，孩子能解决真实数据处理问题 |

---

## 教学反思

| 维度 | 记录 |
|------|------|
| 学生掌握率 | 预计 70% 学生能完成挑战1，50% 完成挑战2 |
| 常见困惑 | dict 和 set 的符号都是 {} 需要反复区分 |
| 改进方向 | 增加更多 set + dict 配合的实际场景案例 |
| 备注 | 迷你画图程序可留在课后作为兴趣项目继续完善 |
```
