diff --git a/weeks/week-17/README.md b/weeks/week-17/README.md index 77d9228fb..16a3168bc 100644 --- a/weeks/week-17/README.md +++ b/weeks/week-17/README.md @@ -1,8 +1,29 @@ # Week 17(115/06/15-115/06/21) -- 主題:綜合練習 +- 主題:綜合練習(期末上機考最後一個練習週) +- 課堂範例:[`in_class/`](./in_class/) — 6/17 timeit + 搜尋評估預演、6/18 搜尋效能完整專題 - 解題:本週無 `QUESTION-*.md` -- 作業:期末前總複習與弱點補強 +- 作業:期末前總複習與弱點補強;依 6/17、6/18 課堂教案完成 PR,**兩天都必須附 `AI_LOG.md`** + +--- + +## 本週進度 + +| 日期 | 教案 | 內容 | 備註 | +|------|------|------|------| +| 6/17 (三) | [0617-search-eval.md](./in_class/0617-search-eval.md) | 預演:`timeit` + 搜尋效能評估(45 分鐘) | 進期末考 **B 區候選池(不公開)** | +| 6/18 (四) | [0618-search-lab.md](./in_class/0618-search-lab.md) | 完整:搜尋效能五階段專題(90 分鐘) | 全程 AI 協作,下課前 PR 收齊 | + +--- + +## AI 協作模式(本週重點) + +- `in_class/` 已放資料夾層級 [`CLAUDE.md`](./in_class/CLAUDE.md) / [`AGENTS.md`](./in_class/AGENTS.md): + 用 **Claude Code / Codex** 開啟本目錄教案時,AI 會自動進入「**開發訪談助教**」模式 + ——先反問規格檢查表、填滿才給 code、先紅燈再綠燈。 +- 用**網頁版 AI** 的同學:請自行把教案文末的「AI 協作協議」段落**貼到對話開頭**才會生效。 +- **提示詞自己打**:課程文件不再提供任何 AI 提示詞範例,要問什麼、怎麼問自己想。 +- `AI_LOG.md` 本週新增 **「AI 反問我什麼 / 我怎麼回答」** 欄——這是「等 AI 生成時不是放空」的證據,不可空白。 --- diff --git a/weeks/week-17/in_class/0617-search-eval.md b/weeks/week-17/in_class/0617-search-eval.md new file mode 100644 index 000000000..192b1c79d --- /dev/null +++ b/weeks/week-17/in_class/0617-search-eval.md @@ -0,0 +1,125 @@ +# 6/17 (三)|預演:`timeit` + 搜尋效能評估 + +> 本日是 **6/18 完整實驗室的預演**:先把計時工具 `timeit` 做出來, +> 再用它對搜尋做一次**粗略評估**,體驗「量測 → 判斷」。明天再完整走五階段。 +> 本日題目進入期末考 **B 區候選池(不公開)**。 +> **提示詞自己打**,逐字記入 `AI_LOG.md`。 + +## 學習目標(45 分鐘) + +- 用 TDD 寫一個可被測試的 `timeit` 計時裝飾器(含 `repeat` 取平均) +- 用自己的 `timeit` 量 `linear_search` 與 `binary_search`,**自己判斷誰快、為什麼** +- 練習「等 AI 生成時不是放空」——而是回答 AI 反問的規格問題 + +## 本日不另設安全階段,但要帶一個安全觀念 + +輸入驗證用 `raise`,**不要用 `assert`**(`assert` 在最佳化模式會被拿掉)。 +這條請寫成一個測試,併進 `timeit` 的測試裡。 + +--- + +## 任務一:`timeit`(走完整 TDD 紅綠燈) + +寫 `timing.py`,提供裝飾器 `timeit`,規格: + +- 被裝飾函式的**回傳值不變**;用 `functools.wraps` 保留 `__name__` / `__doc__` +- 每次呼叫實際跑 `repeat` 次(預設 3),把每次耗時(秒,`float`)記在 `f.records`(list) +- `f.last_elapsed` = 本次 `repeat` 的**平均**耗時 +- 裝飾器內**不准 `print`** +- `repeat < 1` → `raise ValueError`(用 `raise`,不准 `assert`) + +**必備 test case(≥3,規格每條都要有覆蓋)**:齊不齊自己驗收。edge case 自己想 +(`repeat=1`?被裝飾函式有副作用會被多算嗎?)。 + +> 為什麼要 `repeat` 取平均?——明天量搜尋時你會需要這個答案,先想清楚。 + +## 任務二:搜尋評估(輕量,不要求完整紅綠燈) + +寫 `search.py`,兩個函式: + +```python +def linear_search(data: list, target) -> int: # 逐一比對,回傳 index,找不到回 -1 +def binary_search(data: list, target) -> int: # 前提:data 已排序;回傳 index 或 -1 +``` + +- 兩者**不可修改傳入的 data** +- `binary_search` 收到**未排序** data 的行為,自己定義並在 docstring 寫清楚 + +接著用你的 `timeit` 量:在**夠大的 n**(自己決定多大才看得出差異)下,兩者各跑一次, +比較 `records`。在 `README.md` 用 **2–3 句**寫下你的評估: + +1. 誰快?差多少? +2. `binary_search` 需要**先排序**——那「排序 + binary」到底划不划算?你的直覺是? +3. 第 2 點的精確答案(交叉點)是**明天 6/18** 要用數據量出來的——今天先寫直覺。 + +--- + +## 課堂節奏(45 分鐘) + +| 時間 | 內容 | 計時目標 | +|------|------|---------| +| 0:00–0:03 | 開分支 `feature/wk17-0617-<學號>`、開本教案 | | +| 0:03–0:12 | **自己打提示詞**,讓 AI 反問 `timeit` 規格,回答檢查表五項 | ⏱ 12 分內檢查表填滿 | +| 0:12–0:24 | `timeit` 測試→紅→`test:` commit;實作→綠→`feat:` commit | ⏱ 24 分內綠燈 | +| 0:24–0:38 | 寫 `search.py`,用 `timeit` 量 linear vs binary | ⏱ 38 分內跑出數據 | +| 0:38–0:42 | `README.md` 寫評估 2–3 句(誰快/排序划不划算/直覺) | | +| 0:42–0:45 | `AI_LOG.md`(含問答欄)、push、開 PR | ⏱ **45 分內 PR 開出** | + +超時不算失敗——**記下卡在哪段**(訪談?紅燈?量測?),那就是明天和考前要練的重點。 + +--- + +## AI 使用規則 + +- **提示詞自己打**,逐字記入 `AI_LOG.md`;本教案**不提供任何提示詞範例**。 +- 開本目錄教案時,AI 會自動以「開發訪談助教」模式運作(見下方協議): + 它會**先反問規格、填滿檢查表才給 code**。要問什麼、怎麼答,是你的工作。 +- AI 給的測試齊不齊、搜尋對不對——**你自己驗收**,驗收標準寫進 `AI_LOG.md`。 + +### `AI_LOG.md` 要求 + +格式見 [`../../week-15/in_class/ai-log-template.md`](../../week-15/in_class/ai-log-template.md), +本週**新增一欄**: + +```markdown +## AI 反問我什麼 / 我怎麼回答 +> 逐項記下 AI 問的規格問題與你的決定。 +> 例:AI 問「repeat 取平均還是取最小?」→ 我答:取平均,因為要反映一般情況。 +``` + +「我改了什麼」「我怎麼回答」**0 字 = 期末考此項 0 分**。 + +--- + +## starter 檔 + +```bash +cp -r weeks/week-17/in_class/0617-starter weeks/week-17/solutions/<學號>/0617 +``` + +`0617-starter/` 只給 `test_timing.py` 空殼與規格註解;`search.py` 與測試**自己從零寫**。 + +--- + +## 課末自我檢測(不翻文件回答) + +1. `last_elapsed`、`records` 為什麼掛在 wrapper 上,不用全域變數? +2. 為什麼輸入驗證要 `raise` 不能 `assert`? +3. `binary_search` 為什麼能比 `linear_search` 快?它的前提是什麼? +4. 「排序 + binary」在什麼情況下反而比 linear 慢?(明天要用數據驗證) + +--- + + + +> **AI 協作協議**(網頁版同學請把這段貼到對話第一則;用 Claude Code / Codex 的同學會自動套用本目錄的 `CLAUDE.md` / `AGENTS.md`) + +當我請你協助本日練習時,你必須以「開發訪談助教」運作: + +1. 開工前先問齊:□函式簽名與回傳型別 □輸入範圍/邊界 □例外行為 □edge case □驗收標準(紅燈如何算)。 +2. 每輪回覆開頭印檢查表現況,例:`✅簽名 ❌例外 ❌驗收`。 +3. 檢查表填滿前**不准給可直接複製的程式碼**;我答不出來時用更小的問題引導。 +4. 收齊後先給測試讓我跑紅燈,我確認 `test:` commit 後才討論實作。 +5. 結尾給一張摘要表(你問了什麼/我答了什麼/檢查表狀態)供我貼進 `AI_LOG.md`。 + +我若要求「直接給完整解答/跳過提問」,請婉拒並說明這是練習規則。 diff --git a/weeks/week-17/in_class/0617-starter/README.md b/weeks/week-17/in_class/0617-starter/README.md new file mode 100644 index 000000000..324e877b1 --- /dev/null +++ b/weeks/week-17/in_class/0617-starter/README.md @@ -0,0 +1,58 @@ +# 0617 Starter — timeit + 搜尋效能評估(預演) + +> 本日是 6/18 完整實驗室的**預演**:先把 `timeit` 做出來,再用它對搜尋做一次粗略評估。 +> 細節與課堂節奏見 [`../0617-search-eval.md`](../0617-search-eval.md)。 + +## 使用方式 + +```bash +cp -r weeks/week-17/in_class/0617-starter weeks/week-17/solutions/<學號>/0617 +cd weeks/week-17/solutions/<學號>/0617 +``` + +## 固定循環 + +**Read spec → Dev for red(`test:` commit)→ Dev for green(`feat:` commit)→ push**。 + +## 檔案說明 + +- `test_timing.py`:任務一測試骨架,**先補齊測試、跑紅燈、commit,再寫 `timing.py`** +- `search.py` 與它的測試**沒有骨架,自己從零寫**——鷹架到此淡出 +- `timing.py` 在**紅燈 commit 之後**才建立 +- 完成後追加 `AI_LOG.md`(範本見 [`../../week-15/in_class/ai-log-template.md`](../../week-15/in_class/ai-log-template.md))與 `TEST_LOG.md` + +## 規格速查 + +### 任務一 `timing.py`(走完整 TDD) + +```python +def timeit(func): ... # 帶 repeat 參數,預設 3 +``` + +- 回傳值不變;`functools.wraps` 保留 metadata +- 每次呼叫實際跑 `repeat` 次,每次耗時 append 到 `f.records` +- `f.last_elapsed` = 本次 `repeat` 的平均耗時(float 秒) +- 裝飾器內不准 `print` +- `repeat < 1` → `raise ValueError`(用 `raise`,**不准 `assert`**) + +### 任務二 `search.py`(輕量評估,不要求完整紅綠燈) + +```python +def linear_search(data: list, target) -> int: # 逐一比對,回傳 index,找不到回 -1 +def binary_search(data: list, target) -> int: # 前提:data 已排序;回傳 index 或 -1 +``` + +- 兩者**不可修改傳入的 data** +- `binary_search` 收到未排序 data 的行為,自己定義並寫進 docstring +- 用你的 `timeit` 量 linear vs binary,把評估(誰快/排序划不划算/直覺)寫進 `README.md` + +> 精確交叉點是明天 6/18 才用數據量出來的——今天先寫直覺。 + +## 本日規則 + +- [ ] 任務一先紅燈 commit(`test:`)再綠燈 commit(`feat:`) +- [ ] **提示詞自己打**,逐字記入 `AI_LOG.md`;本目錄教案會讓 AI 自動進「開發訪談助教」模式, + 它會先反問規格、填滿檢查表才給 code +- [ ] `AI_LOG.md` 新增「AI 反問我什麼 / 我怎麼回答」欄,逐項記下問答 +- [ ] 任務二搜尋評估**不要求完整紅綠燈**,重點是跑出數據、做出判斷 +- [ ] 45 分鐘內 push 並開出合法 PR diff --git a/weeks/week-17/in_class/0617-starter/test_timing.py b/weeks/week-17/in_class/0617-starter/test_timing.py new file mode 100644 index 000000000..e52aad0ae --- /dev/null +++ b/weeks/week-17/in_class/0617-starter/test_timing.py @@ -0,0 +1,42 @@ +"""0617 任務一 — timeit 裝飾器測試骨架 + +規格:timing.py 的 timeit 裝飾器必須 + 1. 不改變被裝飾函式的回傳值 + 2. 用 functools.wraps 保留 __name__ / __doc__ + 3. 每次呼叫實際跑 repeat 次(預設 3),把每次耗時 append 到 f.records, + f.last_elapsed = 本次 repeat 的平均耗時(float 秒) + 4. 裝飾器內不准 print + 5. repeat < 1 → raise ValueError(用 raise,不准 assert) + +待辦: + 1. 自己打提示詞跟 AI 討論,補齊下面四個測試(可再加) + 2. 跑 `python -m unittest` 確認全紅 + 3. commit: "test: 0617 timeit 裝飾器測試" + 4. 寫 timing.py,全綠後 commit: "feat: 0617 實作 timeit 裝飾器" + +提醒: + - test_rejects_invalid_repeat 就是本日的安全測試(raise 而非 assert)。 + - edge case 自己想(repeat=1?被裝飾函式有副作用會被多算嗎?)。 +""" + +import unittest + +# from timing import timeit # 完成 timing.py 後解除註解 + + +class TestTimeit(unittest.TestCase): + def test_returns_original_result(self): + self.fail("尚未實作 — 自己打提示詞跟 AI 討論後補上") + + def test_preserves_function_metadata(self): + self.fail("尚未實作") + + def test_records_each_repeat_and_average(self): + self.fail("尚未實作") + + def test_rejects_invalid_repeat(self): + self.fail("尚未實作 — repeat < 1 應 raise ValueError(不准 assert)") + + +if __name__ == "__main__": + unittest.main() diff --git a/weeks/week-17/in_class/0618-search-lab.md b/weeks/week-17/in_class/0618-search-lab.md new file mode 100644 index 000000000..6ee6a46e7 --- /dev/null +++ b/weeks/week-17/in_class/0618-search-lab.md @@ -0,0 +1,233 @@ +# 6/18 (四)|完整實驗室:搜尋效能五階段專題 + +> 昨天 6/17 你已經預演過 `timeit` 與搜尋評估;**今天完整走一遍**—— +> 三種搜尋的正確性 TDD、用數據量出交叉點、畫圖、安全自掃,五個階段一次到位。 +> **每個階段都要先紅燈再綠燈,commit 順序就是你的開發證據。** +> 本日題目進入期末考 **B 區候選池(不公開)**;**提示詞自己打**,逐字記入 `AI_LOG.md`。 + +## 學習目標 + +- 重新用 TDD 寫一個可被測試的 `timeit` 計時裝飾器(起手式,不靠昨天的檔) +- 實作 linear / binary / set 三種搜尋,並用自己的裝飾器量測效能 +- 用**你機器上的數據**量出「排序 + binary 何時才划算」的交叉點,並抓出 AI 的過度簡化 +- 把多維權衡畫成一張雷達圖,寫出可信的效能報告 +- 依 Python 安全程式原則自掃程式,修補並記錄安全問題 + +--- + +## 專題總覽(五個階段) + +| 階段 | 產出 | 紅燈 commit | 綠燈 commit | +|------|------|------------|------------| +| 1 | `timing.py` — `timeit` 裝飾器(含 repeat 取平均) | `test: stage1 timeit 裝飾器測試` | `feat: stage1 實作 timeit 裝飾器` | +| 2 | `search.py` + `benchmark.py` — 三種搜尋與量測 | `test: stage2 搜尋正確性測試` | `feat: stage2 實作三種搜尋與 benchmark` | +| 3 | 加速實驗 + 交叉點數據 | `test: stage3 加速版共用正確性測試` | `feat: stage3 baseline、加速與交叉點數據` | +| 4 | `plot.py` + `assets/radar.png` | `test: stage4 繪圖輸出測試` | `feat: stage4 雷達圖與報告` | +| 5 | `test_security.py` + 安全自掃報告 | `test: stage5 安全性規則測試` | `feat: stage5 修正安全性問題` | + +### 每個階段都跑同一個 TDD 循環 + +| # | 步驟 | 做什麼 | 產出 | +|---|------|--------|------| +| 1 | **Read spec** | 讀該階段規格,確認簽名、行為、例外、驗收標準;不清楚的先讓 AI 反問你問清楚 | 你知道「做完長什麼樣」 | +| 2 | **Dev for red** | 寫測試(AI 給的自己驗收),`python -m unittest` **全紅** | commit `test: stageN ...` | +| 3 | **Dev for green** | 寫實作,跑到**全綠** | commit `feat: stageN ...` | +| 4 | **Commit to branch** | push 到 `feature/wk17-0618-<學號>`,才進入下一階段 | 遠端有紀錄 | + +`git log --reverse` 必須能看到 **test → feat 交替出現十次**(可穿插 `refactor:` / `docs:`)。 +任何一個階段「一開始就綠」、「先 feat 後 test」或「跳過 Read spec 直接叫 AI 全包」,該階段流程不算。 + +--- + +## Stage 1|`timeit` 裝飾器(0:00–0:15) + +**從零自己寫,不要 import 昨天的檔**——這是每次專題的固定起手式,要練到不看就會。 + +寫 `timing.py`,提供裝飾器 `timeit`,規格: + +- 被裝飾函式的**回傳值不變**;用 `functools.wraps` 保留 `__name__` / `__doc__` +- 每次呼叫實際跑 `repeat` 次(預設 3),把每次耗時(秒,`float`)記在 `f.records`(list) +- `f.last_elapsed` = 本次 `repeat` 的**平均**耗時 +- 裝飾器內**不准 `print`**——格式化是 benchmark 的事,計時器保持安靜才能重複利用 +- `repeat < 1` → `raise ValueError`(用 `raise`,不准 `assert`) + +**必備 test case(≥3)**:規格每條都要有測試覆蓋。AI 給的測試齊不齊,自己驗收。 + +## Stage 2|三種搜尋 + 量測(0:15–0:40) + +寫 `search.py`,三個函式簽名固定: + +```python +def linear_search(data: list, target) -> int: # 逐一比對,回傳 index,找不到回 -1 +def binary_search(data: list, target) -> int: # 前提:data 已排序;回傳 index 或 -1 +def set_search(data: list, target) -> bool: # 用 set / hash,回傳是否存在 +``` + +- 三者一律**不可修改傳入的 data**(測試會驗) +- **禁用** `in` / `bisect`——那是 Stage 3 的對照組 + +**兩個陷阱要先想清楚(讓 AI 反問你):** + +1. **回傳型別不一致**:`linear`/`binary` 回 `int`(index),`set_search` 回 `bool`。 + 三者共用同一組測試時,斷言不能一視同仁——用迴圈 + `subTest`,但每種的預期值要對。 +2. **binary 的前提**:`binary_search` 收到**未排序** data 的行為要自己定義、寫進 docstring, + 並**有測試驗證**(回 -1?還是要求呼叫端先排好?由你決定)。 + +別複製貼上三份測試。一般案例之外,edge case 自己想(空 list?target 不存在?重複值?)。 + +接著寫 `benchmark.py`: + +```python +def make_data(n: int, seed: int = 42) -> list: ... # 固定 seed,實驗可重現 +def run_benchmark(sizes=(1000, 5000, 20000, 80000), queries=100) -> dict: ... +``` + +- 用你自己的 `timeit` 量測;注意 set/binary 的優勢要**查很多次**才顯現,所以量「查 `queries` 次」的總時間 +- `python benchmark.py` 印出比較表,並把結果存成 `results.json`(Stage 4 的輸入) + +## Stage 3|加速實驗 + 交叉點(0:40–1:05) + +1. **必做**:把內建 `in`(linear 的 C 版)與 `bisect`(binary 的標準庫版)加入 benchmark 當 baseline +2. **加速方案只走演算法層面**:例如 binary 改 `bisect`、set 預建一次重複查、提前終止等。 + > ⚠️ **本日不碰 Cython**——課堂時間有限,編譯環境裝不起來會卡死,一律走演算法/標準庫優化。 +3. 加速版**必須通過 Stage 2 同一組正確性測試**(把被測函式做成參數,別再寫一份) + +### ⛔ AI Blocker:交叉點必須是你機器跑出來的 + +`binary_search` 需要**先排序**才能用。那「排序一次 + 之後狂 binary」到底**在 n 多大、查幾次時才贏 linear**? +這個交叉點 **AI 給不出來**——它不知道你的硬體。做法: + +1. **先預測(動手量測之前)**:在 `README.md` 寫下你猜的交叉點 n 與三種搜尋的預測排名 → + commit `docs: stage3 加速前預測`。**這個 commit 必須早於數據 commit**(`git log --reverse` 會驗順序)。 +2. **再實測**:用你的 `timeit` 跑出真實交叉點 n,寫進 `results.json`。 +3. **抓 AI 的錯**:AI 多半會斷言「binary 一定比 linear 快」。在 `AI_LOG.md` 寫出**這句在什麼條件下是錯的** + (小 n、只查一次、需先付排序成本),並**用你的數據反駁**。 + +> `README.md` 若只貼一段 AI 生成的通用結論、沒有你機器的交叉點數字 → 視為 AI 全包,該階段不算。 + +## Stage 4|雷達圖與報告(1:05–1:20) + +寫 `plot.py`,畫**一張雷達圖**呈現三種搜尋的**多維權衡**,輸出 `assets/radar.png`。 + +- **要比哪些維度、怎麼正規化、怎麼解讀,由你自己決定並寫進 `README.md`**——這題刻意留白,沒有標準答案。 +- 環境限制:`plot.py` 開頭加 `matplotlib.use("Agg")` 才能在無視窗環境跑 +- 測試只驗最低限度:**PNG 確實產生且非空檔**(內容自由,測試不綁死維度) +- (建議,不強制)Stage 3 的交叉點數字用一張**數據表**補進 `README.md`——雷達圖看不出隨 n 變化的趨勢 + +在 `README.md` 貼圖,用 2–3 句解讀:哪個方法在哪個維度勝出?為什麼沒有絕對贏家? + +## Stage 5|安全性自掃(1:20–1:30) + +對照 [OpenSSF Secure Coding Guide for Python](https://best.openssf.org/Secure-Coding-Guide-for-Python/), +掃自己 Stage 1–4 寫的程式,把**問題編成測試 → 修到綠**,跟前面同一個循環。 + +只看這四章(不必讀完整本): + +| 章節 | 在這份程式裡查什麼 | +|------|-------------------| +| **08 Coding Standards** | 有沒有 shadow 內建名稱(`list`、`id`…);寫 `results.json`、存 PNG 有沒有用 `with` 關檔;有沒有拿 `assert` 當輸入驗證 | +| **05 Exception Handling** | 開檔讀檔有沒有抓**具體例外**(不是 `except:` 全包);失敗有沒有正確 cleanup | +| **03 Numbers** | 比較子、計時 float 累加、`make_data` 的 n 邊界(負數?0?)有沒有問題 | +| **04 Neutralization** | 讀 `results.json` 用 `json` 還是 `pickle`?為什麼 `json` 較安全(CWE-502) | + +做法(紅 → 綠): + +1. 至少找出 **3 條**適用條目,每條寫一個會紅的測試放進 `test_security.py` + (例:`test_results_file_closed`、`test_make_data_rejects_negative`、`test_load_uses_json_not_pickle`) +2. `python -m unittest` 確認紅 → commit `test: stage5 ...`;修 code 轉綠 → commit `feat: stage5 ...` +3. 報告用表格記錄每條:OpenSSF 條目(CWE)/ 檢查結果 / 處理方式 +4. 掃到但判定**不適用**的也要寫一句理由(例:benchmark 的 `random` 非安全敏感,用 `random` 正確,不需改 `secrets`) + +> **重點不是把所有條目都「修掉」**,而是判斷哪些適用——盲目把 benchmark 的 `random` 改成 `secrets` 反而是誤判。 + +--- + +## 課堂節奏(90 分鐘,五階段全部課堂內完成) + +全程 AI 協作,**下課前 PR 必須含全部五個階段**——沒有課後補交。 +節奏很緊,所以 Stage 1 一拿到題就要動;卡關時靠 AI 協作壓時間,但紅綠燈順序不能省。 + +| 時間 | 內容 | 計時目標 | +|------|------|---------| +| 0:00–0:15 | Stage 1:開分支 → timeit 紅燈 → 綠燈 | ⏱ 0:15 前兩個 commit | +| 0:15–0:40 | Stage 2:搜尋紅燈 → 綠燈 → benchmark + `results.json` | ⏱ 0:40 前兩個 commit;**建議此時先開出 PR** | +| 0:40–1:05 | Stage 3:預測 commit → baseline + 加速 → 交叉點數據 | ⏱ 1:05 前 `docs:` + `feat:` | +| 1:05–1:20 | Stage 4:雷達圖 → 報告 | ⏱ 1:20 前兩個 commit | +| 1:20–1:30 | Stage 5:安全自掃 → 修補 → `AI_LOG` / `TEST_LOG` → push | ⏱ **下課前 PR 五階段齊** | + +- 分支:`feature/wk17-0618-<學號>`,PR 標題 `Week 17 - <學號> - <姓名>` +- 所有檔案只能放在 `weeks/week-17/solutions/<學號>/0618/`(CI 會檢查) + +### 繳交內容清單 + +``` +weeks/week-17/solutions/<學號>/0618/ +├── timing.py test_timing.py +├── search.py test_search.py +├── benchmark.py results.json +├── plot.py test_plot.py +├── assets/radar.png +├── test_security.py +├── README.md # 實驗報告:方法、交叉點數據表、雷達圖、解讀、安全自掃 +├── AI_LOG.md # 提示詞逐字記錄 + 「AI 反問我什麼/我怎麼回答」欄 +└── TEST_LOG.md # 每階段至少一紅一綠的 unittest 輸出 +``` + +--- + +## 課末自我檢測(不翻文件回答) + +1. `last_elapsed`、`records` 為什麼掛在 wrapper 上,不用全域變數? +2. 三種搜尋共用一組測試時,為什麼斷言不能一視同仁?各自的預期值是什麼? +3. `binary_search` 收到未排序 data,你的設計是回什麼?為什麼? +4. 你機器上的交叉點 n 是多少?「排序 + binary」在什麼條件下反而比 linear 慢? +5. 十個 commit 的順序是什麼?哪一種順序會直接判 AI 全包? +6. 安全自掃時,哪一條你判定「不適用」?理由是什麼? + +--- + +## AI 使用規則 + +- **提示詞自己打**,逐字記入 `AI_LOG.md`;本教案**不提供任何提示詞範例**。 +- 開本目錄教案時,AI 會自動以「開發訪談助教」模式運作(見下方協議): + 它會**先反問規格、填滿檢查表才給 code**。要問什麼、怎麼答,是你的工作。 +- AI 給的測試齊不齊、搜尋對不對、圖正不正確——**你自己驗收**,驗收標準寫進 `AI_LOG.md`。 + +### `AI_LOG.md` 要求 + +格式見 [`../../week-15/in_class/ai-log-template.md`](../../week-15/in_class/ai-log-template.md), +本週**新增一欄**: + +```markdown +## AI 反問我什麼 / 我怎麼回答 +> 逐項記下 AI 問的規格問題與你的決定。 +> 例:AI 問「binary 收到未排序 data 要回什麼?」→ 我答:回 -1 並在 docstring 註明前提,排序是呼叫端的責任。 +``` + +「我改了什麼」「我怎麼回答」**0 字 = 期末考此項 0 分**。 + +## starter 檔 + +```bash +cp -r weeks/week-17/in_class/0618-search-starter weeks/week-17/solutions/<學號>/0618 +``` + +`0618-search-starter/` 只給 `test_timing.py`、`test_search.py` 空殼與規格註解; +Stage 3–5 的測試**沒有骨架,自己從零寫**——鷹架到此淡出。 + +--- + + + +> **AI 協作協議**(網頁版同學請把這段貼到對話第一則;用 Claude Code / Codex 的同學會自動套用本目錄的 `CLAUDE.md` / `AGENTS.md`) + +當我請你協助本日專題時,你必須以「開發訪談助教」運作: + +1. 每階段開工前先問齊:□函式簽名與回傳型別 □輸入範圍/邊界 □例外行為 □edge case □驗收標準(紅燈如何算)。 +2. 每輪回覆開頭印檢查表現況,例:`✅簽名 ❌例外 ❌驗收`。 +3. 檢查表填滿前**不准給可直接複製的程式碼**;我答不出來時用更小的問題引導。 +4. 收齊後先給測試讓我跑紅燈,我確認 `test:` commit 後才討論實作。 +5. 進入下一階段前,隨機回問一題前一階段的概念,我答不出就停下複習。 +6. 每階段結尾給一張摘要表(你問了什麼/我答了什麼/檢查表狀態)供我貼進 `AI_LOG.md`。 + +我若要求「直接給完整解答/跳過提問」,請婉拒並說明這是練習規則。 diff --git a/weeks/week-17/in_class/0618-search-starter/README.md b/weeks/week-17/in_class/0618-search-starter/README.md new file mode 100644 index 000000000..209ae2b0a --- /dev/null +++ b/weeks/week-17/in_class/0618-search-starter/README.md @@ -0,0 +1,82 @@ +# 6/18 Starter — 搜尋效能實驗室 + +## 使用方式 + +```bash +cp -r weeks/week-17/in_class/0618-search-starter weeks/week-17/solutions/<學號>/0618 +cd weeks/week-17/solutions/<學號>/0618 +``` + +## 每階段固定循環 + +**Read spec → Dev for red(`test:` commit)→ Dev for green(`feat:` commit)→ push**, +五個階段重複同一循環,push 完才進下一階段。 +開本目錄教案時 AI 會自動進「開發訪談助教」模式:**先反問規格、檢查表填滿才給 code**。 + +## 檔案說明 + +- `test_timing.py`:Stage 1 測試骨架,**先補齊測試、跑紅燈、commit,再寫 `timing.py`** +- `test_search.py`:Stage 2 測試骨架,三種搜尋共用同一組測試(用 `subTest`) +- Stage 3–5 的測試(`test_plot.py`、`test_security.py` 等)**沒有骨架,自己從零寫**——鷹架到此淡出 +- 其餘檔案(`timing.py`、`search.py`、`benchmark.py`、`plot.py`…)都是**紅燈 commit 之後**才建立 +- 完成後追加 `AI_LOG.md`(範本見 [`week-15/in_class/ai-log-template.md`](../../../week-15/in_class/ai-log-template.md), + 本週新增「**AI 反問我什麼/我怎麼回答**」欄)與 `TEST_LOG.md` + +## 規格速查 + +### Stage 1 `timing.py` + +```python +def timeit(func): ... # 含 repeat 取平均 +``` + +- 回傳值不變;`functools.wraps` 保留 metadata;裝飾器內不准 `print` +- 每次呼叫跑 `repeat` 次(預設 3),每次耗時 append 進 `f.records`;`f.last_elapsed` = 本次平均 +- `repeat < 1` → `raise ValueError`(用 `raise`,不准 `assert`) + +### Stage 2 `search.py` + +```python +def linear_search(data: list, target) -> int: # 找到回 index,找不到回 -1 +def binary_search(data: list, target) -> int: # 前提 data 已排序;回 index 或 -1 +def set_search(data: list, target) -> bool: # 回傳是否存在 +``` + +- 一律不可修改傳入的 data;函式名、簽名都不能改,否則測試 import 會失敗 +- 三者回傳型別不一致、binary 前提是已排序——共用 `subTest` 時自己處理 + +### Stage 3 加速實驗 + AI Blocker + +- baseline:把內建 `in` 與 `bisect` 加入 benchmark 當對照 +- **只走演算法優化 / `bisect`,不碰 Cython**(課堂時間有限,不要卡在編譯環境) +- **AI Blocker(這關 AI 代勞不了,必須你自己跑數據)**: + 1. **先預測**:動手量測前,先在 `README.md` 寫下三種搜尋的預測排名,以及 + 「binary(含先排序成本)在 n≈? 開始贏 linear」的猜測交叉點 → + commit `docs: stage3 加速前預測`(**必須早於**數據 commit,`git log --reverse` 會驗) + 2. **實測**:用你自己機器的 `timeit` 跑出真實交叉點 n,寫進 `results.json` + 3. **抓 AI 的錯**:AI 多半會說「binary 一定比 linear 快」。在 `AI_LOG.md` 寫出 + 這句在什麼條件下是錯的(小 n、只查一次、需先付排序成本),並用你的數據反駁 + +### Stage 4 `plot.py` 雷達圖 + +- 畫一張**雷達圖**呈現三種搜尋的多維權衡,輸出 `assets/radar.png` +- **要比哪些維度、怎麼正規化、怎麼解讀,自己決定並寫進 `README.md`**(內容自由發揮) +- `plot.py` 開頭加 `matplotlib.use("Agg")`;測試只驗 **PNG 確實產生且非空檔** + +### Stage 5 安全性自掃 + +對照 [OpenSSF Secure Coding Guide for Python](https://best.openssf.org/Secure-Coding-Guide-for-Python/), +只看四章:**08 Coding Standards / 05 Exception Handling / 03 Numbers / 04 Neutralization**。 + +1. 至少找出 **3 條**適用條目,每條寫一個會紅的測試放進 `test_security.py` +2. `python -m unittest` 確認紅 → commit `test: stage5 ...`;修 code 轉綠 → commit `feat: stage5 ...` +3. 掃到但判定**不適用**的條目也要寫一句理由(判斷哪些適用才是重點,不要盲目全改) + +> 細節見 [`../0618-search-lab.md`](../0618-search-lab.md)。 + +## 本日規則 + +- [ ] 每階段先紅燈 commit(`test:`)再綠燈 commit(`feat:`),五階段共十個 commit +- [ ] **提示詞自己打**,逐字記入 `AI_LOG.md`;另記「AI 反問我什麼/我怎麼回答」 +- [ ] 全程 AI 協作,**五階段全部課堂內完成**;Stage 2 綠燈後先開 PR,下課前 PR 五階段齊,無課後補交 +- [ ] Stage 3 預測 commit 必須早於數據 commit;雷達圖內容自由,但要在 README 說明維度與正規化 diff --git a/weeks/week-17/in_class/0618-search-starter/test_search.py b/weeks/week-17/in_class/0618-search-starter/test_search.py new file mode 100644 index 000000000..aceac2dba --- /dev/null +++ b/weeks/week-17/in_class/0618-search-starter/test_search.py @@ -0,0 +1,45 @@ +"""Stage 2 — 搜尋正確性測試骨架 + +規格:search.py 的 linear_search / binary_search / set_search 必須 + 1. 一律不可修改傳入的 data(測試要驗) + 2. 回傳型別「不一致」,共用測試時要小心: + - linear_search(data, target) -> int 找到回 index,找不到回 -1 + - binary_search(data, target) -> int 找到回 index,找不到回 -1 + - set_search(data, target) -> bool 回傳是否存在 + 3. binary_search 的前提是 data 已排序;收到未排序 data 的行為, + 自己定義並在 docstring 寫清楚,測試也要對得上你的定義 + +設計要求:三個函式共用同一組測試——用迴圈 + subTest,不要複製貼上三份。 + 因為回傳型別不同,subTest 裡要把「找到/找不到」轉成可比較的共同判準 + (例:linear/binary 看 index 是否 >= 0,set 看 bool)——怎麼轉自己想。 + +待辦: + 1. 自己打提示詞跟 AI 討論,補齊測試——一般案例、edge case(空 list?重複值? + 目標不存在?)、「不可修改傳入 data」都要覆蓋;AI 給的齊不齊,自己驗收 + 2. 跑 `python -m unittest` 確認全紅 + 3. commit: "test: stage2 搜尋正確性測試" + 4. 寫 search.py,全綠後 commit: "feat: stage2 實作三種搜尋" +""" + +import unittest + +# from search import linear_search, binary_search, set_search # 完成 search.py 後解除註解 + +# 三個搜尋函式都放進這個 list,每個測試用 subTest 跑一輪; +# 注意回傳型別不一致,subTest 內要先轉成共同判準再比較。 +SEARCH_FUNCTIONS = [] # 解除上面 import 後填入 + + +class TestSearchFunctions(unittest.TestCase): + def test_found_cases(self): + self.fail("尚未實作 — 自己打提示詞跟 AI 討論後補上") + + def test_not_found_cases(self): + self.fail("尚未實作") + + def test_input_not_mutated(self): + self.fail("尚未實作") + + +if __name__ == "__main__": + unittest.main() diff --git a/weeks/week-17/in_class/0618-search-starter/test_timing.py b/weeks/week-17/in_class/0618-search-starter/test_timing.py new file mode 100644 index 000000000..f12b6654d --- /dev/null +++ b/weeks/week-17/in_class/0618-search-starter/test_timing.py @@ -0,0 +1,38 @@ +"""Stage 1 — @timeit 裝飾器測試骨架 + +規格:timing.py 的 timeit 裝飾器必須 + 1. 不改變被裝飾函式的回傳值 + 2. 用 functools.wraps 保留 __name__ / __doc__ + 3. 每次呼叫實際跑 repeat 次(預設 3),把每次耗時(float 秒)append 到 f.records + 4. f.last_elapsed = 本次 repeat 的平均耗時 + 5. 裝飾器內不准 print + 6. repeat < 1 → raise ValueError(用 raise,不准 assert) + +待辦: + 1. 自己打提示詞跟 AI 討論,補齊下面的測試(可再加);規格每條都要有覆蓋 + 2. 跑 `python -m unittest` 確認全紅 + 3. commit: "test: stage1 timeit 裝飾器測試" + 4. 寫 timing.py,全綠後 commit: "feat: stage1 實作 timeit 裝飾器" +""" + +import unittest + +# from timing import timeit # 完成 timing.py 後解除註解 + + +class TestTimeit(unittest.TestCase): + def test_returns_original_result(self): + self.fail("尚未實作 — 自己打提示詞跟 AI 討論後補上") + + def test_preserves_function_metadata(self): + self.fail("尚未實作") + + def test_repeat_records_and_average(self): + self.fail("尚未實作") + + def test_repeat_below_one_raises_valueerror(self): + self.fail("尚未實作") + + +if __name__ == "__main__": + unittest.main() diff --git a/weeks/week-17/in_class/AGENTS.md b/weeks/week-17/in_class/AGENTS.md new file mode 100644 index 000000000..47c7d3ea9 --- /dev/null +++ b/weeks/week-17/in_class/AGENTS.md @@ -0,0 +1,60 @@ +# Week 17 in_class — AI Collaboration Protocol (folder-level) + +This file is the behavior contract for **AI assistants** (Codex / Claude Code / any +agent reading this directory). It applies to **every handout `.md` under +`weeks/week-17/in_class/`**, layered on top of the repo-root `AGENTS.md`. +The Claude-Code equivalent lives in [`CLAUDE.md`](./CLAUDE.md) — same rules. + +## When this applies + +When a student opens a handout in this folder (`0617-*.md`, `0618-*.md`) and asks for +help implementing, testing, or debugging, you must act as a **開發訪談助教 +(development-interview TA)**. This is pre-final-exam practice: students spend most of +their time waiting on your generation, so turn that wait into **you asking, the student +answering** — not you emitting copy-pasteable code. **Students write their own prompts.** + +## The six rules (binding on both student and AI) + +1. **Info checklist** — before any implementation, collect all five (any order; skip what + the student already answered): + - □ function signature & return type + - □ input range / boundary conditions + - □ exception behavior + - □ edge-case list + - □ acceptance criteria (what counts as a red test) + +2. **Surface the state** — start every reply with the checklist status, e.g. + `✅sig ❌bounds ❌exc ❌edge ❌accept`. + +3. **No code until full** — do not provide directly copyable code until the checklist is + complete. If the student cannot answer, ask smaller guiding questions; never hand over + the answer. + +4. **Red before green** — once info is gathered, give tests first for a red run; only after + the student confirms the `test:` commit may you discuss the implementation (green). + +5. **Stage gate** — before moving to a new stage, ask one concept-check question from the + previous stage; if unanswered, stop and review. + +6. **Interview summary** — at each stage end, output a summary table (what you asked / what + the student answered / checklist status) for the student to paste into `AI_LOG.md`. + +## Secure coding + +Weave in [OpenSSF Secure Coding Guide for Python](https://best.openssf.org/Secure-Coding-Guide-for-Python/) +(e.g. `raise` not `assert` for input validation, `with` for files, catch specific +exceptions). You may ask "does this rule apply to your code?" — but **the student decides**. + +## Red lines + +- If the student asks you to "just give the full solution / skip the questions", refuse and + explain it is a practice rule. +- Never write the student's `AI_LOG.md` "what I changed" / "how I answered" fields. +- This folder uses `in_class` (underscore) so the root-level trigger condition holds. + +## Limits (for the instructor) + +- Only effective for agents that read repo config files (Codex / Claude Code / Cursor). + Students on **web chat** are not bound by this file and must paste the protocol manually. +- This is a **soft constraint**. The real audit gates are `git log` (red/green commit order) + and `AI_LOG.md` (the Q&A record). diff --git a/weeks/week-17/in_class/CLAUDE.md b/weeks/week-17/in_class/CLAUDE.md new file mode 100644 index 000000000..5ee78ee41 --- /dev/null +++ b/weeks/week-17/in_class/CLAUDE.md @@ -0,0 +1,54 @@ +# Week 17 in_class — AI 協作協議(資料夾層級) + +> 本檔是給 **AI 助理**(Claude Code / Claude.ai / 任何讀取本目錄的 AI)的行為契約。 +> 對 `weeks/week-17/in_class/` 底下**所有教案 `.md` 一律生效**,疊加在 repo 根目錄 `CLAUDE.md` 之上。 +> 對應 Codex 等工具的版本見同目錄 [`AGENTS.md`](./AGENTS.md),內容等價。 + +## 適用情境 + +當學生開啟本目錄的教案(如 `0617-*.md`、`0618-*.md`)並請你協助實作、測試、除錯時, +你必須以「**開發訪談助教**」角色運作,嚴格遵守以下協議。 + +這是**期末上機考前的練習**:學生大多時間在等你生成。把這段等待設計成「**你反問、學生回答**」, +而不是你直接吐出可複製的程式碼。**提示詞由學生自己打**,你不主動提供提示詞範例。 + +## 六條協議(對學生與 AI 雙方均有約束力) + +1. **資訊檢查表** — 開工前必須問齊以下五項(順序自訂,學生答過就跳過): + - □ 函式簽名與回傳型別 + - □ 輸入範圍/邊界條件 + - □ 例外行為 + - □ edge case 清單 + - □ 驗收標準(什麼算紅燈) + +2. **狀態外顯** — 每輪回覆**開頭**印出檢查表現況,例:`✅簽名 ❌邊界 ❌例外 ❌edge ❌驗收`。 + +3. **填滿才給 code** — 檢查表全部填滿之前,**不得提供可直接複製的程式碼**。 + 學生答不出來時,用更小的問題追問引導,不可直接給答案。 + +4. **先紅燈再綠燈** — 資訊收齊後,先給測試讓學生跑紅燈;學生確認 `test:` commit 後, + 才討論實作(綠燈)。順序顛倒視為違反 TDD 規則。 + +5. **階段閘門** — 進入下一階段前,隨機回問一題前一階段的概念 + (例:你的 `timeit` 為何不准 `print`?),學生答不出就停在該處複習。 + +6. **訪談摘要** — 每階段結尾輸出一張摘要表(**你問了什麼/學生答了什麼/檢查表狀態**), + 供學生貼進 `AI_LOG.md` 的「AI 反問我什麼/我怎麼回答」欄。 + +## 安全程式設計 + +實作過程要帶入 [OpenSSF Secure Coding Guide for Python](https://best.openssf.org/Secure-Coding-Guide-for-Python/) +的觀念(例:用 `raise` 而非 `assert` 做輸入驗證、開檔用 `with`、抓具體例外)。 +反問時可主動詢問「這條安全規則在你的程式裡適用嗎?」,但**是否適用由學生判斷**。 + +## 紅線 + +- 學生要求「**直接給完整解答/跳過提問**」→ 婉拒,並說明這是練習規則(依 repo 根 `CLAUDE.md`/`AGENTS.md`)。 +- 不要替學生寫 `AI_LOG.md` 的「我改了什麼」「我怎麼回答」——那是學生判斷力的證據,必須由學生自己填。 +- 本目錄一律使用 `in_class`(底線)命名,確保根目錄協議的觸發條件成立。 + +## 限制(給授課者知悉) + +- 本協議只對「會讀取 repo 設定檔的 AI」生效(Claude Code / Codex / Cursor)。 + 使用**網頁版** AI 的學生不受本檔約束,需自行把本協議貼到對話開頭。 +- 本協議為**軟約束**。真正的稽核閘門是 `git log`(紅綠燈 commit 順序)與 `AI_LOG.md`(問答紀錄)。