Skip to content
Closed
27 changes: 27 additions & 0 deletions weeks/week-18/solutions/1114405020/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AI_LOG.md (1114405020 - 范芯瑜)

## 我問 AI 什麼
1. 「請為我讀取 week-18 的期末考 PDFHandout,並解算出我的學號 (1114405020) 對應的個人化參數。」
2. 「請以 TDD 模式(先寫測試跑紅燈,再寫實作轉綠燈),帶領我完成期末考的第一題(資料清理)、第二題(凱撒密碼)、第三題(數字根)與第四題(搜尋效能)。」
3. 「請協助產出 Matplotlib 極座標雷達圖繪圖程式 `plot.py`,比較線性與二分搜尋的多維度表現。」

## AI 給了什麼
1. 計算出專屬參數:$D = 2$、$\text{SHIFT} = 1$、$\text{base} = 2$、$K = 120$。
2. 提供了符合 4 道題目 TDD unittest 設計用例,以及對應的手打實作版、教學易讀版程式碼。
3. 給出了完整的雷達圖繪製模型程式,包含 polar 極座標與閉合設定。

## 我改了什麼
1. **補齊 TDD 空列表與大數 Edge Cases**:我發現 AI 起初給的測試只涵蓋了基本題意,我主動要求並在 `test_data_cleaning.py` 中補上空列表 `[]` 測試,在 `test_digital_root.py` 中補上大數 $10^9$ 與 $0$ 的邊界測試,並在 `test_caesar.py` 補上了非英文字元、空格以及大小寫字母循環邊界。
2. **優化數字根基底代算邏輯**:在 `digital_root.py` 中,我要求 AI 提供一個通用型的數字根基底轉換邏輯(利用除法取餘數與整除),而不僅僅是使用 `bin(x)` 快捷函式。這樣做的好處是代碼極具擴充性,未來若基底變更為 8、16 甚至其他基底,只需要修改 `base` 參數即可無痛重用,體現了高質量的編碼標準。
3. **雷達圖中文亂碼之警告處理**:在執行 `plot.py` 時,Matplotlib 會拋出 `UserWarning: Glyph missing from font(s)`。我主動在維度標籤中加上了英文名稱(如 `Small N Speed\n(小 N 速度)`),確保在無視窗 Linux 終端機環境中即使缺乏中文字體,也能完美顯示英文標籤。

---

## AI 反問我什麼 / 我怎麼回答

| AI 詢問的規格問題 | 我做出的決定與回答 |
|---|---|
| **AI 問**:「第一題中,去除重複值時要怎麼處理順序?D = 2 整除如何判定?」 | **我答**:必須保留第一次出現的順序,所以要用 `seen` 集合判重、並用 list 依序保存。整除則直接用 `% 2 == 0` 判定。 |
| **AI 問**:「第二題凱撒密碼中,SHIFT = 1。如果英文字母位移超出邊界(如 Z 或 z),該怎麼循環?」 | **我答**:大寫英文字母在 A-Z 循環,小寫在 a-z 循環。位移轉換必須轉到 0-25 範圍做加位移與對 26 取餘數(`% 26`),再轉換回 ASCII,其餘非字母字元直接原樣保留。且必須用 `sys.stdin` 讀到 EOF 結束。 |
| **AI 問**:「第三題任意進位中,base = 2。0 的數字根要怎麼處理?輸入邊界到哪裡?」 | **我答**:輸入 $x$ 範圍在 $0 \le x \le 10^9$ 至 EOF。當 $x = 0$ 時,數字根直接為 0,其餘大數則重複換算成 base 各位數相加直至小於 base (2)。 |
| **AI 問**:「第四題二分搜尋效能中,搜尋目標 K = 120。陣列應該要多大?如何輸出比較次數與耗時?」 | **我答**:陣列大小必須 $\ge 10^5$,我採用長度為 $100,000$ 的升冪偶數數列。比較次數必須在搜尋中用變數累加記錄,並以 `FOUND idx cmp=次數` 格式輸出。時間部分用 `time.perf_counter()` 計算,並以 `linear : 0.000003 s` 格式印出,最後輸出誰比較快。 |
70 changes: 70 additions & 0 deletions weeks/week-18/solutions/1114405020/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 國立澎湖科技大學 期末上機考實驗報告

## 學生資訊
- **班級**:日四技資工系一甲
- **學號**:1114405020
- **姓名**:范芯瑜
- **座號**:20

---

## 依學號決定之個人化參數與計算

本機上機考依學號 **1114405020** 最後兩碼 **`20`**(十位數字 `2`,個位數字 `u = 0`)對照計算出之參數:
- **第一題:整除數 D** = $0 \pmod 4 + 2 = \mathbf{2}$
- **第二題:位移 SHIFT** = $0 \pmod{25} + 1 = \mathbf{1}$
- **第三題:進位基底 base** = $\mathbf{2}$(由對照表 $u=0$ 得 base = 2)
- **第四題:搜尋目標 K** = $100 + 20 = \mathbf{120}$

---

## 📊 各題實作與效能數據報告

### ❶ 第一題:資料清理 (Data Cleaning)
- **實作邏輯**:
1. 接收整數數列,使用 `set` 去除重複值,同時維持首次出現的保序結構。
2. 篩選出能被 $D = 2$ 整除的偶數。
3. 使用 Timsort 由小到大排序。
- **測試驗證**:已補齊包含空輸入 `[]`、負數邊界值 `[-8, 12, 16]` 等 5 項自動化測試,全數通過。

### ❷ 第二題:凱撒密碼 (Caesar Cipher)
- **實作邏輯**:
- 讀入多行字串,將其中的英文字母向後位移 $\text{SHIFT} = 1$ 位(A-Z 及 a-z 獨立循環)。
- 非英文字母原樣保留。
- 使用 `sys.stdin` 迴圈處理,完美支援終端機 EOF 讀取。

### ❸ 第三題:任意進位數字根
- **實作邏輯**:
- 將十進位非負整數 $x$ 轉換為基底 $\text{base} = 2$(二進位)。
- 反覆將二進位各位數相加,直到結果小於 $2$ 為止(即為二進位下的單一位數 `0` 或 `1`)。
- 對於 $x > 0$,由於其二進位必定包含至少一個 `1`,反覆相加的最終數字根皆為 `1`;$x = 0$ 則為 `0`。

### ❹ 第四題:二分搜尋效能評估
在包含 $10^5$(100,000)個元素的升冪排序數列(皆為偶數)中,搜尋目標 $K = 120$:

* **搜尋結果**:`FOUND 60 cmp=16`
* 目標 $120$ 位於索引 **$60$**(即 $120 / 2$)。
* 二分搜尋在 $100,000$ 筆資料中,僅花了 **$16$ 次** 比較便精準鎖定目標!
* **效能對照結果**:
* **線性搜尋耗時**:`0.000003` 秒
* **二分搜尋耗時**:`0.000002` 秒
* **結論**:二分搜尋速度快於線性搜尋 (`=> binary_search faster`)。
* *註:由於 K=120 在數列的極前端(index 60),線性搜尋僅掃描了 61 次便命中,故耗時極短;若 K 位在數列尾端(例如 199998),線性搜尋耗時將大幅飆升,而二分搜尋依然能穩定在 17 次比較內完成。*

---

## 📈 線性搜尋 vs 二分搜尋多維度雷達圖

我們針對線性搜尋(Linear)與二分搜尋(Binary)在以下 5 個關鍵效能指標上進行評分(1 ~ 5 分,分數越高代表在該維度上表現越優異、越理想):
1. **小 N 速度 (Small N Speed)**:小數據下兩者皆極快(線性 5 分,二分 4 分)。
2. **大 N 速度 (Large N Speed)**:大數據下二分搜尋 $O(\log n)$ 具絕對優勢(線性 1 分,二分 5 分)。
3. **免排序度 (No Pre-sorting)**:線性搜尋無需預處理,直接查詢(線性 5 分,二分 1 分)。
4. **實作簡易度 (Simplicity)**:線性搜尋編寫極易、不易出錯(線性 5 分,二分 3 分)。
5. **最壞比較優度 (Worst-case Cmp)**:最壞情況下二分搜尋比較次數極少(線性 1 分,二分 5 分)。

### 雷達圖對照( assets/radar.png ):

![Radar Chart](assets/radar.png)

### 雙演算法多維權衡分析(2-3 句):
由雷達圖可以看出,**搜尋演算法中並無絕對完美的勝者,其選用取決於應用情境**。線性搜尋在「免排序度」與「實作簡意度」上取得滿分,極度適合資料量小且頻繁變動、不常查詢的場景。而二分搜尋雖需付出一次性排序的高昂開銷且實作較複雜,但它擁有完美的「大 N 速度」與「最壞比較優度」,最適合用於資料靜態不變、但需要極高頻率進行大規模檢索的唯讀系統中。
165 changes: 165 additions & 0 deletions weeks/week-18/solutions/1114405020/TEST_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# TEST_LOG.md (1114405020 - 范芯瑜)

本記錄包含期末上機考 A、B、C 區所有 4 道題目的 TDD 紅綠燈測試 unittest 終端機輸出日誌。

---

## ❶ 第一題:資料清理 (Data Cleaning)

### 🔴 TDD 紅燈測試 (Red Light)
* **命令**:`python3 -m unittest test_data_cleaning.py`
* **輸出**:
```text
E
======================================================================
ERROR: test_data_cleaning (unittest.loader._FailedTest.test_data_cleaning)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_data_cleaning
Traceback (most recent call last):
File "/usr/lib/python3.14/unittest/loader.py", line 137, in loadTestsFromName
module = __import__(module_name)
File "/home/faxyu729/2026-python/weeks/week-18/solutions/1114405020/test_data_cleaning.py", line 2, in <module>
from data_cleaning import clean_data
ModuleNotFoundError: No module named 'data_cleaning'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
```

### 🟢 TDD 綠燈測試 (Green Light)
* **命令**:`python3 -m unittest test_data_cleaning.py`
* **輸出**:
```text
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK
```

---

## ❷ 第二題:凱撒密碼 (Caesar Cipher)

### 🔴 TDD 紅燈測試 (Red Light)
* **命令**:`python3 -m unittest test_caesar.py`
* **輸出**:
```text
E
======================================================================
ERROR: test_caesar (unittest.loader._FailedTest.test_caesar)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_caesar
Traceback (most recent call last):
File "/usr/lib/python3.14/unittest/loader.py", line 137, in loadTestsFromName
module = __import__(module_name)
File "/home/faxyu729/2026-python/weeks/week-18/solutions/1114405020/test_caesar.py", line 2, in <module>
from caesar import encrypt_caesar
ModuleNotFoundError: No module named 'caesar'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
```

### 🟢 TDD 綠燈測試 (Green Light)
* **命令**:`python3 -m unittest test_caesar.py`
* **輸出**:
```text
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK
```

---

## ❸ 第三題:任意進位數字根 (Digital Root)

### 🔴 TDD 紅燈測試 (Red Light)
* **命令**:`python3 -m unittest test_digital_root.py`
* **輸出**:
```text
E
======================================================================
ERROR: test_digital_root (unittest.loader._FailedTest.test_digital_root)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_digital_root
Traceback (most recent call last):
File "/usr/lib/python3.14/unittest/loader.py", line 137, in loadTestsFromName
module = __import__(module_name)
File "/home/faxyu729/2026-python/weeks/week-18/solutions/1114405020/test_digital_root.py", line 2, in <module>
from digital_root import find_digital_root
ModuleNotFoundError: No module named 'digital_root'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
```

### 🟢 TDD 綠燈測試 (Green Light)
* **命令**:`python3 -m unittest test_digital_root.py`
* **輸出**:
```text
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK
```

---

## ❹ 第四題:二分搜尋效能 (Search Performance)

### 🔴 TDD 紅燈測試 (Red Light)
* **命令**:`python3 -m unittest test_search_perf.py`
* **輸出**:
```text
E
======================================================================
ERROR: test_search_perf (unittest.loader._FailedTest.test_search_perf)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_search_perf
Traceback (most recent call last):
File "/usr/lib/python3.14/unittest/loader.py", line 137, in loadTestsFromName
module = __import__(module_name)
File "/home/faxyu729/2026-python/weeks/week-18/solutions/1114405020/test_search_perf.py", line 2, in <module>
from search_perf import binary_search_perf, linear_search_perf
ModuleNotFoundError: No module named 'search_perf'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)
```

### 🟢 TDD 綠燈測試 (Green Light)
* **命令**:`python3 -m unittest test_search_perf.py`
* **輸出**:
```text
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
```

---

## 🌟 最終所有題目的自動化測試聯合執行 — 全綠燈 (All PASS)
* **命令**:`python3 -m unittest test_data_cleaning.py test_caesar.py test_digital_root.py test_search_perf.py`
* **輸出**:
```text
...................
----------------------------------------------------------------------
Ran 19 tests in 0.001s

OK
```
**期末上機考所有 4 個大題、19 項測試案例皆已 100% 通過綠燈驗收!**
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions weeks/week-18/solutions/1114405020/caesar-easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import sys


def encrypt_caesar(text: str, shift: int = 1) -> str:
"""【AI 教學版】凱撒密碼加密

包含詳細繁體中文註解,適合教學與初學者閱讀。
"""
result = []
for char in text:
# 處理大寫字母
if "A" <= char <= "Z":
# ord(char) 取得字元的 ASCII 值,減去 'A' 基準點得到 0-25 的索引值
# 加上位移量 (shift) 後對 26 取餘數以達到循環(例如 Z 往後移 1 變成 A)
# 最後加回 ord('A') 並用 chr() 轉換回英文字母字元
shifted_index = (ord(char) - ord("A") + shift) % 26
result.append(chr(shifted_index + ord("A")))
# 處理小寫字母
elif "a" <= char <= "z":
shifted_index = (ord(char) - ord("a") + shift) % 26
result.append(chr(shifted_index + ord("a")))
# 非英文字母直接原樣保留(標點符號、空格、中文、數字等)
else:
result.append(char)
return "".join(result)


if __name__ == "__main__":
# 透過 sys.stdin 讀取標準輸入,會自動分行且完美處理 EOF (Ctrl+D 或檔案結尾)
for line in sys.stdin:
# line 結尾可能包含換行符,先使用 rstrip 去除,加密後再用 write 輸出並補上一個換行
clean_line = line.rstrip("\r\n")
encrypted = encrypt_caesar(clean_line, 1) # 學號末碼為 0,SHIFT = 1
sys.stdout.write(encrypted + "\n")
29 changes: 29 additions & 0 deletions weeks/week-18/solutions/1114405020/caesar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import sys


def encrypt_caesar(text: str, shift: int = 1) -> str:
"""凱撒密碼位移加密:
1. 僅英文字母進行位移,其餘字元保留不變
2. 大寫英文字母 (A-Z) 在其範圍內循環
3. 小寫英文字母 (a-z) 在其範圍內循環
"""
result = []
for char in text:
if "A" <= char <= "Z":
# 轉換至 0-25 範圍進行位移,再轉回 ASCII
new_char = chr((ord(char) - ord("A") + shift) % 26 + ord("A"))
result.append(new_char)
elif "a" <= char <= "z":
# 轉換至 0-25 範圍進行位移,再轉回 ASCII
new_char = chr((ord(char) - ord("a") + shift) % 26 + ord("a"))
result.append(new_char)
else:
result.append(char)
return "".join(result)


if __name__ == "__main__":
# 處理多行輸入至 EOF
for line in sys.stdin:
# 去除結尾換行符號、加密、並重組輸出
sys.stdout.write(encrypt_caesar(line.rstrip("\r\n"), 1) + "\n")
25 changes: 25 additions & 0 deletions weeks/week-18/solutions/1114405020/data_cleaning-easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def clean_data(nums: list[int], d: int) -> list[int]:
"""【AI 教學版】資料清理

此版本包含詳細的繁體中文註解,解釋每一步的設計與時間複雜度權衡。
"""
# 輸入驗證:若除數小於 1 則拋出 ValueError,保護程式免於零除或非法運算
if d < 1:
raise ValueError("除數 d 必須是大於或等於 1 的整數")

# 步驟 1:去除重複(保留第一次出現的順序)
# 使用 seen (set) 達到 O(1) 的超快尋找速度,同時用 list 保存順序,整體複雜度為 O(n)
seen = set()
deduped = []
for num in nums:
if num not in seen:
seen.add(num)
deduped.append(num)

# 步驟 2:篩選能被 d 整除的數
# 使用 Python 的 List Comprehension 寫法,乾淨且高效,複雜度為 O(n)
filtered = [num for num in deduped if num % d == 0]

# 步驟 3:由小到大排序
# Python 內建的 sorted 使用 Timsort 演算法,時間複雜度為 O(k log k),其中 k ≦ n
return sorted(filtered)
23 changes: 23 additions & 0 deletions weeks/week-18/solutions/1114405020/data_cleaning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
def clean_data(nums: list[int], d: int) -> list[int]:
"""資料清理:
1. 去除重複(保留第一次出現的順序)
2. 只保留能被 d 整除的數
3. 由小到大排序
若 d < 1,拋出 ValueError。
"""
if d < 1:
raise ValueError("d must be an integer >= 1")

# 步驟 1: 去重保序
seen = set()
deduped = []
for num in nums:
if num not in seen:
seen.add(num)
deduped.append(num)

# 步驟 2: 只保留能被 d 整除的數
filtered = [num for num in deduped if num % d == 0]

# 步驟 3: 由小到大排序
return sorted(filtered)
Loading
Loading