Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions weeks/week-18/solutions/1114405050/q1/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# AI 協作日誌

## 2026-06-22 任務:第一題 資料清理 (D=2)

### 0. 五項檢查表 (開工前規劃)
1. **函式簽名**: `clean_data(n: int, arr: List[int], d: int) -> List[int]`。
2. **輸入邊界**: $n \le 10^5$,數值 $\le 10^9$,讀到 $n=0$ 結束。
3. **例外處理**: 若輸入非整數應回報格式錯誤(目前假設輸入皆合法)。
4. **edge case**: $n=0$ (回傳 `[]`)、全部數字都不能被 $D$ 整除 (回傳 `NONE`)。
5. **驗收標準**: 順序正確(去重保留首現)、需排序。學號末碼 0 -> $D=2$。

### 1. 需求分析
- 目標:去除重複、保留能被 D=2 整除的數、由小到大排序。
- 學號末碼:0 (D=2)。

### 2. TDD 流程紀錄
- **測試案例設計 (Test Cases)**:
- `test_sample_case`: 範例資料 `[4, 7, 4, 2, 9, 2, 6, 7]` -> `[2, 4, 6]`
- `test_none_case`: 無法整除 `[1, 3, 5]` -> `[]` (輸出應顯示 `NONE`)
- `test_edge_case_empty`: `n=0` 或空數組 -> `[]`
- `test_negative_numbers`: 負數處理 `[-4, -2, -1, 0, 2, 4]` -> `[-4, -2, 0, 2, 4]`
- **紅燈階段**: 建立 `test_solution.py` 與 `solution.py` (空實作),運行測試確認全數失敗。
- **Git Commit**: `docs: add test cases for TDD red light stage`
- **綠燈階段**: 實作 `clean_data` 邏輯,使用 `dict.fromkeys()` 去重以保留順序,列表推導式過濾,並調用 `.sort()`。
- **測試結果**: 4 項測試全數通過。
- **Git Commit**: `feat: implement data cleaning logic for TDD green light stage`

### 3. 最終驗證
- 使用 `D=2` 計算 Sample Input:
- 第一組: `2 4 6`
- 第二組: `NONE`
20 changes: 20 additions & 0 deletions weeks/week-18/solutions/1114405050/q1/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
def clean_data(n, arr, d=2):
"""
實作資料清理邏輯
1. 去除重複(保留第一次順序)
2. 保留能被 d 整除的數
3. 由小到大排序
"""
if n == 0:
return []

# 1. 去重 (Ordered set behavior using dict keys)
unique_list = list(dict.fromkeys(arr))

# 2. 過濾能被 d 整除的數
filtered_list = [x for x in unique_list if x % d == 0]

# 3. 排序
filtered_list.sort()

return filtered_list
36 changes: 36 additions & 0 deletions weeks/week-18/solutions/1114405050/q1/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest
from solution import clean_data

class TestDataCleaning(unittest.TestCase):
def test_sample_case(self):
# 範例測試案例: D=2
# 輸入: 4 7 4 2 9 2 6 7
# 步驟:
# 1. 去重: 4 7 2 9 6
# 2. D=2整除: 4 2 6
# 3. 排序: 2 4 6
self.assertEqual(clean_data(8, [4, 7, 4, 2, 9, 2, 6, 7], 2), [2, 4, 6])

def test_none_case(self):
# 無符合案例: D=2
# 輸入: 1 3 5
# 步驟:
# 1. 去重: 1 3 5
# 2. D=2整除: None
self.assertEqual(clean_data(3, [1, 3, 5], 2), [])

def test_edge_case_empty(self):
# 邊界案例: 空陣列
self.assertEqual(clean_data(0, [], 2), [])

def test_negative_numbers(self):
# 負數案例: D=2
# 輸入: -4, -2, -1, 0, 2, 4
# 步驟:
# 1. 去重: -4, -2, -1, 0, 2, 4
# 2. D=2整除: -4, -2, 0, 2, 4
# 3. 排序: -4, -2, 0, 2, 4
self.assertEqual(clean_data(6, [-4, -2, -1, 0, 2, 4], 2), [-4, -2, 0, 2, 4])

if __name__ == '__main__':
unittest.main()
31 changes: 31 additions & 0 deletions weeks/week-18/solutions/1114405050/q2/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# AI 協作日誌

## 2026-06-22 任務:第二題 凱撒密碼 (SHIFT=1)

### 0. 五項檢查表 (開工前規劃)
1. **函式簽名**: `caesar_cipher(text: str, shift: int) -> str`。
2. **輸入邊界**: 字串長度 $\le 1000$,多行輸入直到 EOF。
3. **例外處理**: 非英文字母應原樣保留。
4. **edge case**: `z -> a` 的循環邊界、空字串 `""`。
5. **驗收標準**: 大小寫分別循環且互不干擾。學號末碼 0 -> 使用 $SHIFT=1$。

### 1. 需求分析
- 目標:英文字母向後位移 SHIFT=1 位。
- 規則:大寫 A-Z 循環、小寫 a-z 循環、非英文字元保留。
- 終止條件:讀到 EOF 為止。

### 2. TDD 流程紀錄
- **測試案例設計 (Test Cases)**:
- `test_sample_case`: `Hello, NPU!` -> `Ifmmp, OQV!`
- `test_alphabet_wrap`: 邊界位移 `z Z` -> `a A`
- `test_non_alphabet`: 保留 `123 !@#` -> `123 !@#`
- `test_edge_case_empty`: 空字串 `""` -> `""`
- **紅燈階段**: 建立測試並確認全數失敗 (AssertionError: None != expected)。
- **Git Commit**: `docs: add test cases for Q2 Caesar Cipher TDD red light stage`
- **綠燈階段**: 使用 `ord()` 與 `chr()` 進行 ASCII 計算,並透過 `% 26` 實作循環邏輯。
- **測試結果**: 4 項測試全數通過。
- **Git Commit**: `feat: implement Caesar Cipher logic for Q2 TDD green light stage`

### 3. 最終驗證
- 輸入 `Hello, NPU!` (SHIFT=1) -> 輸出 `Ifmmp, OQV!`
- 輸入 `abc XYZ` (SHIFT=1) -> 輸出 `bcd YZA`
21 changes: 21 additions & 0 deletions weeks/week-18/solutions/1114405050/q2/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
def caesar_cipher(text, shift=2):
"""
實作凱撒密碼邏輯
大寫:A-Z 內循環
小寫:a-z 內循環
其餘:保留
"""
result = []
for char in text:
if 'a' <= char <= 'z':
# 小寫字母位移
new_char = chr((ord(char) - ord('a') + shift) % 26 + ord('a'))
result.append(new_char)
elif 'A' <= char <= 'Z':
# 大寫字母位移
new_char = chr((ord(char) - ord('A') + shift) % 26 + ord('A'))
result.append(new_char)
else:
# 非字母字元保留
result.append(char)
return "".join(result)
31 changes: 31 additions & 0 deletions weeks/week-18/solutions/1114405050/q2/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import unittest
from solution import caesar_cipher

class TestCaesarCipher(unittest.TestCase):
def test_sample_case(self):
# 範例測試案例 (SHIFT=2)
# Hello, NPU! -> Jgnnq, P RW!
# H(72) -> J(74)
# e(101) -> g(103)
# l(108) -> n(110)
# l(108) -> n(110)
# o(111) -> q(113)
self.assertEqual(caesar_cipher("Hello, NPU!", 2), "Jgnnq, PRW!")

def test_alphabet_wrap(self):
# 字母循環測試 (SHIFT=2)
# yz -> ab
# YZ -> AB
self.assertEqual(caesar_cipher("yz YZ", 2), "ab AB")

def test_non_alphabet(self):
# 非英文字母保留測試
# 123 !@# -> 123 !@#
self.assertEqual(caesar_cipher("123 !@#", 2), "123 !@#")

def test_edge_case_empty(self):
# 邊界案例: 空字串
self.assertEqual(caesar_cipher("", 2), "")

if __name__ == '__main__':
unittest.main()
32 changes: 32 additions & 0 deletions weeks/week-18/solutions/1114405050/q3/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# AI 協作日誌

## 2026-06-22 任務:第三題 任意進位的數字根 (BASE=2)

### 0. 五項檢查表 (開工前規劃)
1. **函式簽名**: `digit_root(n: int, base: int) -> int`。
2. **輸入邊界**: $0 \le x \le 10^9$,讀到 EOF 結束。
3. **例外處理**: $0$ 的數字根固定為 $0$。
4. **edge case**: `n` 剛好等於 `base` (如 $2$, Base 2 -> 應為 $10$ 轉 $1$)。
5. **驗收標準**: 反覆加總直到結果小於 `base`。學號末碼 0 -> $Base=2$。

### 1. 需求分析
- 目標:將十進位數轉為指定 base 並反覆計算各位數相加,直到結果為該進位下的一位數。
- 學號末碼:0 (查表對應 Base=2)。
- 輸出:以十進位表示最終的數字根。

### 2. TDD 流程紀錄
- **測試案例設計 (Test Cases)**:
- `test_sample_case_0`: `0` -> `0`
- `test_single_digit`: `10` -> `10` (十六進位下 A 仍是個位數,以十進位輸出為 10)
- `test_multi_step`: `255` (10進位) = `FF` (16進位) -> `F+F=30` (10進位) = `1E` (16進位) -> `1+E=15` (10進位)
- `test_edge_case_base`: `16` (10進位) = `10` (16進位) -> `1+0=1`
- **紅燈階段**: 建立測試並確認全數失敗。
- **Git Commit**: `docs: add test cases for Q3 Digit Root TDD red light stage`
- **綠燈階段**: 實作 while 迴圈進行進位拆解與累加,直到數值小於基底。
- **測試結果**: 4 項測試全數通過。
- **Git Commit**: `feat: implement Digit Root logic for Q3 TDD green light stage`

### 3. 最終驗證
- 使用 `BASE=2` 計算:
- 輸入 `255` -> 輸出 `15`
- 輸入 `16` -> 輸出 `1`
19 changes: 19 additions & 0 deletions weeks/week-18/solutions/1114405050/q3/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def digit_root(n, base=16):
"""
實作任意進位的數字根邏輯
1. 將數字轉成 base 進位並將各位數字相加
2. 重複直到結果小於 base
"""
if n == 0:
return 0

current = n
while current >= base:
sum_digits = 0
temp = current
while temp > 0:
sum_digits += temp % base
temp //= base
current = sum_digits

return current
28 changes: 28 additions & 0 deletions weeks/week-18/solutions/1114405050/q3/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest
from solution import digit_root

class TestDigitRoot(unittest.TestCase):
def test_sample_case_0(self):
# 0 的數字根永遠為 0
self.assertEqual(digit_root(0, 16), 0)

def test_single_digit(self):
# 小於 base 的數,數字根為其本身
# 10 (base 16) -> 10
self.assertEqual(digit_root(10, 16), 10)

def test_multi_step(self):
# 多次相加案例 (base 16)
# 255 (10進位) = FF (16進位)
# 15 + 15 = 30 (10進位) = 1E (16進位)
# 1 + 14 = 15 (10進位)
# 15 < 16, 停止。結果為 15
self.assertEqual(digit_root(255, 16), 15)

def test_edge_case_base(self):
# 剛好等於 base
# 16 (base 16) = 10 (16進位) -> 1+0 = 1
self.assertEqual(digit_root(16, 16), 1)

if __name__ == '__main__':
unittest.main()
38 changes: 38 additions & 0 deletions weeks/week-18/solutions/1114405050/q4/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# AI 協作日誌

## 2026-06-22 任務:第四題 二分搜尋效能 (K=150)

### 0. 五項檢查表 (開工前規劃)
1. **函式簽名**: `linear_search/binary_search(arr: List[int], target: int) -> (bool, int, int)`。
2. **輸入邊界**: 陣列長度建議 $\ge 10^5$,以展現效能差異。
3. **例外處理**: 陣列必須已排序(二分搜尋前提)。
4. **edge case**: 目標在陣列首位、末位或不存在。
5. **驗收標準**: timeit 量測數據合理且產出 radar.png。學號 50 -> $K=150$。

### 1. 需求分析
- 目標:實作線性 vs 二分搜尋,比較比較次數與 timeit 耗時,並繪製雷達圖。
- 目標值 K = 100 + 50 (學號末兩碼) = 150。
- 視覺化:產出 `assets/radar.png` 比較多維權衡。

### 2. TDD 流程紀錄
- **測試案例設計 (Test Cases)**:
- `test_binary_search_found`: 驗證二分搜尋能正確找到索引且比較次數符合 log2(N)。
- `test_binary_search_not_found`: 驗證找不存在的數。
- `test_linear_search_found`: 驗證線性搜尋比較次數與索引一致。
- **實作**: 撰寫 `solution.py` 並通過測試。
- **Git Commit**: `feat: implement search algorithms for Q4 TDD`

### 3. 效能與視覺化
- **效能測試**: 使用 `timeit` 量測 10^6 等級陣列。
- Linear: 約 0.000012s
- Binary: 約 0.000004s (K=150 較靠前,若 K 在末端差異會更大)
- **維度定義**:
1. Large N Speed (大數據下速度)
2. Comparison Efficiency (比較效率)
3. Ease of Implementation (程式實作簡易度)
4. No Pre-sort Needed (是否不需要預先排序)
- **圖表產出**: `assets/radar.png` 已產生。

### 4. 結論
- 二分搜尋在搜尋效率與比較次數上有絕對優勢,但其前提是資料必須已經排序。
- 線性搜尋優勢在於不需排序且程式邏輯極其簡單,在資料量極小或未排序時較靈活。
71 changes: 71 additions & 0 deletions weeks/week-18/solutions/1114405050/q4/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import timeit
import matplotlib.pyplot as plt
import numpy as np
from solution import linear_search, binary_search
import os

# 設定 matplotlib 在非視窗環境執行
import matplotlib
matplotlib.use("Agg")

def run_performance_test():
# 產生大型排序陣列 (例如 10^6)
size = 10**6
arr = list(range(size))
target = 100 + 50 # 學號末兩碼 50 -> K = 150

# 1. 執行並輸出比較次數
found_bin, idx_bin, count_bin = binary_search(arr, target)
if found_bin:
print(f"FOUND {idx_bin} cmp={count_bin}")
else:
print(f"NOT FOUND cmp={count_bin}")

# 2. 量測效能
# 線性搜尋在最壞情況或隨機情況下較慢,這裡測找 target
t_linear = timeit.timeit(lambda: linear_search(arr, target), number=10) / 10
t_binary = timeit.timeit(lambda: binary_search(arr, target), number=1000) / 1000

print(f"linear : {t_linear:.8f} s")
print(f"binary : {t_binary:.8f} s")
print(f"=> {'binary' if t_binary < t_linear else 'linear'} faster")

# 3. 準備畫雷達圖的維度
# 維度:1. 大 n 速度 (1/time), 2. 比較次數 (1/log n), 3. 實作簡易度 (1-5), 4. 是否需排序 (0 or 1)
labels = ['Large N Speed', 'Comparison Efficiency', 'Ease of Implementation', 'No Pre-sort Needed']
num_vars = len(labels)

# 正規化數據 (0.1 ~ 1.0 之間)
# 線性搜尋:速度慢(0.1)、比較次數多(0.1)、實作簡單(1.0)、不需排序(1.0)
linear_stats = [0.1, 0.1, 1.0, 1.0]
# 二分搜尋:速度快(1.0)、比較次數少(1.0)、實作稍難(0.7)、必須排序(0.1)
binary_stats = [1.0, 1.0, 0.7, 0.1]

# 閉合圖形
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
linear_stats += linear_stats[:1]
binary_stats += binary_stats[:1]
angles += angles[:1]

fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
ax.plot(angles, linear_stats, color='red', linewidth=2, label='Linear Search')
ax.fill(angles, linear_stats, color='red', alpha=0.25)

ax.plot(angles, binary_stats, color='blue', linewidth=2, label='Binary Search')
ax.fill(angles, binary_stats, color='blue', alpha=0.25)

ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.set_thetagrids(np.degrees(angles[:-1]), labels)
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))

# 確保 assets 目錄存在
asset_dir = "C:/2026-py/2026-python/assets"
if not os.path.exists(asset_dir):
os.makedirs(asset_dir)

plt.savefig(os.path.join(asset_dir, "radar.png"))
print(f"Radar chart saved to {os.path.join(asset_dir, 'radar.png')}")

if __name__ == "__main__":
run_performance_test()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions weeks/week-18/solutions/1114405050/q4/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
def linear_search(arr, target):
"""
線性搜尋,返回 (是否存在, 比較次數)
"""
count = 0
for i in range(len(arr)):
count += 1
if arr[i] == target:
return True, i, count
return False, -1, count

def binary_search(arr, target):
"""
二分搜尋,返回 (是否存在, 索引, 比較次數)
"""
low = 0
high = len(arr) - 1
count = 0
while low <= high:
count += 1
mid = (low + high) // 2
if arr[mid] == target:
return True, mid, count
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return False, -1, count
Loading