diff --git a/weeks/week-02/solutions/1112405053/1one.py b/weeks/week-02/solutions/1112405053/1one.py new file mode 100644 index 000000000..efa834fdc --- /dev/null +++ b/weeks/week-02/solutions/1112405053/1one.py @@ -0,0 +1,61 @@ + +import sys +import re +import random + + +def generate_numbers(y: int): + """Generate a deterministic sequence of y numbers. + + The sequence uses y as the random seed so the same input always + produces the same output, which keeps the result reproducible. + """ + rng = random.Random(y) + upper_bound = max(10, y * 2) + return [rng.randint(1, upper_bound) for _ in range(y)] + + +def process_input(text: str): + text = text.strip() + if not text: + return None + try: + y = int(text) + except ValueError: + print("請輸入整數") + return None + if y <= 0: + print("請輸入正整數") + return None + + nums = generate_numbers(y) + print(f"目前數列:{' '.join(map(str, nums))}") + + # remove duplicates while keeping original order + seen = set() + unique = [] + for n in nums: + if n not in seen: + seen.add(n) + unique.append(n) + + # keep numbers divisible by 5 and sort ascending + result = sorted(n for n in unique if n % 5 == 0) + return result + + +def main(): + data = sys.stdin.read() + if not data: + return + res = process_input(data) + if res is None: + return + if res: + print(' '.join(map(str, res))) + else: + print("NONE") + + +if __name__ == '__main__': + main() diff --git a/weeks/week-02/solutions/1112405053/2two.py b/weeks/week-02/solutions/1112405053/2two.py new file mode 100644 index 000000000..125d2ed75 --- /dev/null +++ b/weeks/week-02/solutions/1112405053/2two.py @@ -0,0 +1,32 @@ +import sys + + +def shift_letters(text: str, shift: int = 4) -> str: + """Shift all English letters by 4 positions (Caesar cipher). + Z shifts to D, a shifts to e, etc. Non-letter characters remain unchanged.""" + result = [] + for char in text: + if 'A' <= char <= 'Z': + # Uppercase letters + new_char = chr((ord(char) - ord('A') + shift) % 26 + ord('A')) + result.append(new_char) + elif 'a' <= char <= 'z': + # Lowercase letters + new_char = chr((ord(char) - ord('a') + shift) % 26 + ord('a')) + result.append(new_char) + else: + # Non-letter characters remain unchanged + result.append(char) + return ''.join(result) + + +def main(): + data = sys.stdin.read() + if not data: + return + result = shift_letters(data.rstrip('\n')) + print(result) + + +if __name__ == '__main__': + main() diff --git a/weeks/week-02/solutions/1112405053/3three.py b/weeks/week-02/solutions/1112405053/3three.py new file mode 100644 index 000000000..90c21077d --- /dev/null +++ b/weeks/week-02/solutions/1112405053/3three.py @@ -0,0 +1,110 @@ +import sys + +def to_base3(n: int) -> str: + if n == 0: + return '0' + digits = [] + while n > 0: + digits.append(str(n % 3)) + n //= 3 + return ''.join(reversed(digits)) + + +def iterative_base3_digit_sum_steps(n: int): + """Return a list of step strings and the final single-digit result. + + Each step string has the form: "N -> base3 -> sum: S". + """ + steps = [] + current = n + # handle zero explicitly + if current == 0: + steps.append("0 -> 0 -> sum: 0") + return steps, 0 + b3 = to_base3(current) + current = sum(int(d) for d in b3) + steps.append(f"{n} -> {b3} -> sum: {current}") + + while current >= 10: + digits_sum = sum(int(d) for d in str(current)) + steps.append(f"{current} -> sum: {digits_sum}") + current = digits_sum + return steps, current + + +def run_cli(data: str): + data = data.strip() + if not data: + return + try: + n = int(data) + except ValueError: + print("請輸入整數") + return + steps, result = iterative_base3_digit_sum_steps(n) + for s in steps: + print(s) + print(f"最終結果:{result}") + + +def run_gui(): + try: + import tkinter as tk + from tkinter import messagebox + except Exception: + print("無法載入 Tkinter,請使用命令列模式") + return + + def on_compute(event=None): + txt_output.delete('1.0', tk.END) + raw = entry.get().strip() + if raw == '': + return + try: + n = int(raw) + except ValueError: + messagebox.showerror('錯誤', '請輸入整數') + return + steps, result = iterative_base3_digit_sum_steps(n) + for s in steps: + txt_output.insert(tk.END, s + '\n') + txt_output.insert(tk.END, f"最終結果:{result}\n") + + root = tk.Tk() + root.title('Base-3 迭代加總') + + frm = tk.Frame(root, padx=8, pady=8) + frm.pack(fill=tk.BOTH, expand=True) + + lbl = tk.Label(frm, text='輸入十進位整數:') + lbl.grid(row=0, column=0, sticky='w') + + entry = tk.Entry(frm, width=30) + entry.grid(row=0, column=1, sticky='we') + entry.bind('', on_compute) + + btn = tk.Button(frm, text='計算 (Enter)', command=on_compute) + btn.grid(row=0, column=2, padx=6) + + txt_output = tk.Text(frm, height=10, width=60) + txt_output.grid(row=1, column=0, columnspan=3, pady=8) + + # make columns expand + frm.columnconfigure(1, weight=1) + + entry.focus() + root.mainloop() + + +def main(): + # If there's piped input, run CLI mode. Otherwise open GUI (pressing F5 in an editor + # typically runs the script directly and will show the GUI window). + if not sys.stdin.isatty(): + data = sys.stdin.read() + run_cli(data) + else: + run_gui() + + +if __name__ == '__main__': + main() diff --git a/weeks/week-02/solutions/1112405053/4four/assets/radar.png b/weeks/week-02/solutions/1112405053/4four/assets/radar.png new file mode 100644 index 000000000..6a74f6ce7 Binary files /dev/null and b/weeks/week-02/solutions/1112405053/4four/assets/radar.png differ diff --git a/weeks/week-02/solutions/1112405053/4four/plot.py b/weeks/week-02/solutions/1112405053/4four/plot.py new file mode 100644 index 000000000..93f6d53a7 --- /dev/null +++ b/weeks/week-02/solutions/1112405053/4four/plot.py @@ -0,0 +1,52 @@ +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +# 維度定義 +labels = [ + 'Speed for Large N\n(大N速度)', + 'Speed for Small N\n(小N速度)', + 'No Sorting Required\n(免預先排序)', + 'Implementation Simplicity\n(實作簡易度)', + 'Worst-case Cmps\n(最壞比較次數)' +] +num_vars = len(labels) + +# 線性搜尋與二分搜尋的歸一化得分 (0~1) +# 數值設計呼應 README 中的多維權衡邏輯 +linear_scores = [0.1, 1.0, 1.0, 1.0, 0.1] +binary_scores = [1.0, 0.8, 0.2, 0.6, 1.0] + +# 雷達圖需要首尾相連 +angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist() +linear_scores += linear_scores[:1] +binary_scores += binary_scores[:1] +angles += angles[:1] + +fig, ax = plt.subplots(figsize=(7, 7), subplot_kw=dict(polar=True)) + +# 畫出極座標網格與標籤 +plt.xticks(angles[:-1], labels, color='grey', size=10) + +# 設定 y 軸範圍與刻度 +ax.set_rlabel_position(0) +plt.yticks([0.2, 0.4, 0.6, 0.8, 1.0], ["0.2", "0.4", "0.6", "0.8", "1.0"], color="grey", size=8) +plt.ylim(0, 1.1) + +# 繪製線性搜尋 +ax.plot(angles, linear_scores, linewidth=2, linestyle='solid', label='Linear Search (線性搜尋)', color='#e74c3c') +ax.fill(angles, linear_scores, color='#e74c3c', alpha=0.25) + +# 繪製二分搜尋 +ax.plot(angles, binary_scores, linewidth=2, linestyle='solid', label='Binary Search (二分搜尋)', color='#3498db') +ax.fill(angles, binary_scores, color='#3498db', alpha=0.25) + +# 新增標題與圖例 +plt.title('Multi-Dimensional Trade-off: Linear vs. Binary Search\n(線性 vs 二分搜尋多維權衡雷達圖)', size=14, y=1.1, fontweight='bold') +plt.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1)) + +# 儲存圖片 +plt.tight_layout() +plt.savefig('assets/radar.png', dpi=300) +print("Radar chart generated successfully at assets/radar.png") \ No newline at end of file diff --git a/weeks/week-02/solutions/1112405053/4four/search_eval.py b/weeks/week-02/solutions/1112405053/4four/search_eval.py new file mode 100644 index 000000000..653f7de7e --- /dev/null +++ b/weeks/week-02/solutions/1112405053/4four/search_eval.py @@ -0,0 +1,82 @@ +import timeit +import random +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +# 1. 產生升冪排序的整數陣列 +N = 100000 +# 為了確保能找到, 讓數值範圍涵蓋 153 並且有足夠的大小差距 +# 我們建立一個包含 0 到 200000 之間的奇數或特定間隔, +# 這裡簡單用範圍整數確保 153 必定在其中 +arr = list(range(0, N * 2, 2)) # 產生偶數陣列 +# 強制把 153 放進去,或者我們直接用連續整數確保存在與位置隨機 +arr = list(range(1, N + 1)) + +target = 153 # K = 100 + 53 + +# 2. 實作二分搜尋(計算比較次數) +def binary_search(array, k): + low = 0 + high = len(array) - 1 + cmp_count = 0 + + while low <= high: + cmp_count += 1 + mid = (low + high) // 2 + if array[mid] == k: + return mid, cmp_count + elif array[mid] < k: + low = mid + 1 + else: + high = mid - 1 + return -1, cmp_count + +# 為了比對,也寫一個簡單的線性搜尋計算比較次數 +def linear_search_cmp(array, k): + cmp_count = 0 + for i in range(len(array)): + cmp_count += 1 + if array[i] == k: + return i, cmp_count + return -1, cmp_count + +# 執行搜尋並輸出結果 +idx, cmp_bin = binary_search(arr, target) +if idx != -1: + print(f"FOUND {idx} cmp={cmp_bin}") +else: + print(f"NOT FOUND cmp={cmp_bin}") + +# 3. 用 timeit 量測效能 +# 為了避免線性搜尋在前面太快找到,我們用完整跑完或多次量測的平均 +# linear_search 的原生實作供 timeit 使用 +def run_linear(): + for x in arr: + if x == target: + break + +def run_binary(): + low = 0 + high = len(arr) - 1 + while low <= high: + mid = (low + high) // 2 + if arr[mid] == target: + break + elif arr[mid] < target: + low = mid + 1 + else: + high = mid - 1 + +# 由於二分搜尋極快,我們讓它執行較多次數以利量測 +t_linear = timeit.timeit(run_linear, number=100) +t_binary = timeit.timeit(run_binary, number=100) + +print(f"linear: {t_linear:.6f} s") +print(f"binary: {t_binary:.6f} s") + +if t_binary < t_linear: + print("binary faster") +else: + print("linear faster") \ No newline at end of file diff --git a/weeks/week-02/solutions/1112405053/AI_LOG_1.md b/weeks/week-02/solutions/1112405053/AI_LOG_1.md new file mode 100644 index 000000000..09f533e5c --- /dev/null +++ b/weeks/week-02/solutions/1112405053/AI_LOG_1.md @@ -0,0 +1,82 @@ + +函式設計說明 + +- **函式簽名**: `def name(params...) -> return_type` — 明確列出型別。 +- **參數與回傳值**: 每個參數的型別與語意;回傳值型別與結構說明。 +- **輸入邊界**: 明確允許的值域/型別(含最小/最大/是否含負數、空字串等)。 +- **例外處理**: 哪些錯誤會被拋出或被捕捉並如何回報(例:拋出 `ValueError`、回傳特定錯誤訊息)。 +- **Edge cases**: 列出特殊情況與預期行為(零、非常大值、重複、非整數、空輸入等)。 + + +- **函式簽名**: `def iterative_base3_digit_sum_steps(n: int) -> Tuple[List[str], int]` +- **參數與回傳值**: + - 參數 `n`:整數(十進位)。 + - 回傳:`(steps, result)`,`steps` 是每步字串列表(如 "12 -> 110 -> sum: 2"),`result` 是最後的個位數整數。 +- **輸入邊界**: + - 接受整數類型;建議限制為 `n >= 0`(若要支援負數需明確定義轉換規則)。 + - 非整數輸入視為錯誤/需先轉換。 +- **例外處理**: + - 若 `n` 非整數或 `n < 0`,拋出 `ValueError("請輸入非負整數")`(或在呼叫端顯示錯誤訊息)。 + - GUI 模式應以錯誤視窗提示;CLI 模式列印錯誤並返回非零退出碼(視需要)。 +- **Edge cases**: + - `n = 0`:步驟 `['0 -> 0 -> sum: 0']`,結果 `0`。 + - `0 < n < 10`:不需迭代(`steps` 為空),`result == n`。 + - 非整數字串或空輸入:視為錯誤(提示或拋例)。 + - 極大整數:注意性能與字串長度(可限制最大值或使用更高效處理)。 + + +----- + +檔案:`1one.py` 函式說明 + +1) `process_input` + +- **函式簽名**: `def process_input(text: str) -> list[int]` +- **參數與回傳值**: + - 參數 `text`: 一個包含文字的字串,函式會使用正規表達式從中擷取整數(允許負號)。 + - 回傳:回傳一個整數列表,內容為去重後、原順序保留、且能被 5 整除的數,並以升冪排序(若無符合項目回傳空列表)。 + - 副作用:此函式會列印兩行輸出:`重複數字:X個` 與 `目前數列:...`,因此並非純函式。 +- **輸入邊界**: + - 接受任何字串;若字串中不含數字,`nums` 會是空列表,回傳空列表並列印 `重複數字:0個` 與 `目前數列:`(空)。 + - 支援負整數(會被解析為負值並納入去重/篩選邏輯)。 + - 對於極長字串或大量數字,記憶體與效能會受影響(視使用情況可限制輸入大小)。 +- **例外處理**: + - 若 `text` 不是字串(例如 `None` 或其他型別),`re.findall` 可能會拋出 `TypeError`;呼叫端應先確保傳入字串或在呼叫前轉換。 + - 擷取與轉換整數時使用 `int()`,在目前實作下會只處理由正規表達式擷取出的數字字串,因此通常不會因非整數 token 拋錯。 +- **Edge cases**: + - 空字串或不含數字:列印 `重複數字:0個`,`目前數列:`,回傳 `[]`。 + - 所有數字皆非 5 的倍數:回傳 `[]` 並在 `main` 中印出空行。 + - 重複數字:函式會計算並列印出重複數字的種類數(例如數列中 `4,4,7,7` 會列印 `重複數字:2個`)。 + - 含負數:負數會被當作數字處理,例如 `-5` 會視為可被 5 整除並被包含在結果中。 + +2) `main` + +- **函式簽名**: `def main() -> None` +- **參數與回傳值**: + - 無參數,從標準輸入讀取全部內容(`sys.stdin.read()`)。 + - 無回傳值;會將 `process_input` 的輸出列印到標準輸出(若有結果則以空格分隔列印,否則輸出空行)。 +- **輸入邊界**: + - 依賴標準輸入提供文字內容;若標準輸入為空(例如直接執行且未提供輸入),`main` 會直接返回而不印任何東西。 + - 建議呼叫時以管線或檔案方式提供輸入,或在互動環境將內容貼入。 +- **例外處理**: + - `main` 本身不捕捉 `process_input` 可能拋出的 `TypeError`(若傳入非字串到 `process_input`),因此呼叫端需確保輸入為字串來源(標準輸入通常是字串)。 + - 若 `process_input` 內部有其他未捕捉的錯誤,`main` 會中斷並顯示例外追蹤。 +- **Edge cases**: + - 空輸入:`main` 不產生輸出(直接返回)。 + - `process_input` 回傳空列表:`main` 會輸出一個空行(保留行結尾)。 + - 非數字文字混合:`main` 會交由 `process_input` 處理,非數字文本會被忽略。 + +範例執行: + +```bash +echo "4 7 4 2 9 2 6 7 10" | python 1one.py +``` + +預期輸出(stdout): + +重複數字:3個 +目前數列:4 7 2 9 6 10 +10 + + + diff --git a/weeks/week-02/solutions/1112405053/AI_LOG_2.md b/weeks/week-02/solutions/1112405053/AI_LOG_2.md new file mode 100644 index 000000000..b12076c85 --- /dev/null +++ b/weeks/week-02/solutions/1112405053/AI_LOG_2.md @@ -0,0 +1,68 @@ + +函式設計說明 + +- **函式簽名**: `def name(params...) -> return_type` — 明確列出型別。 +- **參數與回傳值**: 每個參數的型別與語意;回傳值型別與結構說明。 +- **輸入邊界**: 明確允許的值域/型別(含最小/最大/是否含負數、空字串等)。 +- **例外處理**: 哪些錯誤會被拋出或被捕捉並如何回報(例:拋出 `ValueError`、回傳特定錯誤訊息)。 +- **Edge cases**: 列出特殊情況與預期行為(零、非常大值、重複、非整數、空輸入等)。 + + +----- + +檔案:`2two.py` 函式說明 + +1) `shift_letters` + +- **函式簽名**: `def shift_letters(text: str, shift: int = 4) -> str` +- **參數與回傳值**: + - 參數 `text`:任意字串,函式會逐字元處理。 + - 參數 `shift`:位移量,預設為 `4`,代表英文字母往後位移 4 位。 + - 回傳:轉換後的新字串。大寫與小寫分開處理,非英文字母維持原樣。 +- **輸入邊界**: + - `text` 應為字串;內容可包含英文字母、空白、數字、標點符號、換行等。 + - `shift` 建議為整數;目前實作未限制正負,若為負數或大於 26,仍可透過模 26 正常循環。 + - 空字串可正常處理,回傳空字串。 +- **例外處理**: + - 若 `text` 不是字串,逐字迭代時可能發生 `TypeError`;呼叫端應確保輸入型別正確。 + - 若 `shift` 不是整數,`ord(...) + shift` 會在運算時產生型別錯誤;目前函式未額外檢查。 +- **Edge cases**: + - `text = ""`:回傳空字串。 + - `shift = 0`:回傳與原字串相同(英文字母不變)。 + - `shift = 26`:英文字母維持原樣,因為 26 是一整輪循環。 + - `Z` 會變成 `D`,`z` 會變成 `d`,符合 Caesar cipher 規則。 + - 空白、數字、標點符號、中文等非英文字母會原樣保留。 + +2) `main` + +- **函式簽名**: `def main() -> None` +- **參數與回傳值**: + - 無參數,從標準輸入讀取全部內容(`sys.stdin.read()`)。 + - 無回傳值;將加密後的結果列印到標準輸出。 +- **輸入邊界**: + - 預期輸入為字串形式的文字內容,可包含換行。 + - 若輸入尾端有單一換行字元,`main` 會先用 `rstrip('\n')` 去除,避免多印一個空行。 + - 空輸入會直接返回,不輸出任何內容。 +- **例外處理**: + - 若標準輸入讀到的內容不是可處理字串來源,`shift_letters` 可能拋出 `TypeError`;目前 `main` 未額外捕捉。 + - 其他執行錯誤會直接讓程式中止並顯示追蹤資訊。 +- **Edge cases**: + - 空輸入:`main` 不輸出任何內容。 + - 單行輸入:會正常位移英文字母並印出結果。 + - 多行輸入:會保留換行字元,只移動英文字母、其他字元原樣保留。 + - 末尾換行:會被去除,避免多一個空白輸出行。 + +範例執行: + +```bash +echo "HELLO,NPU!" | python 2two.py +echo "abc XYZ" | python 2two.py +``` + +預期輸出(stdout): + +```text +LIPPS,RTY! +efg BCD +``` + diff --git a/weeks/week-02/solutions/1112405053/AI_LOG_3.md b/weeks/week-02/solutions/1112405053/AI_LOG_3.md new file mode 100644 index 000000000..492065009 --- /dev/null +++ b/weeks/week-02/solutions/1112405053/AI_LOG_3.md @@ -0,0 +1,117 @@ + +函式設計說明 + +- **函式簽名**: `def name(params...) -> return_type` — 明確列出型別。 +- **參數與回傳值**: 每個參數的型別與語意;回傳值型別與結構說明。 +- **輸入邊界**: 明確允許的值域/型別(含最小/最大/是否含負數、空字串等)。 +- **例外處理**: 哪些錯誤會被拋出或被捕捉並如何回報(例:拋出 `ValueError`、回傳特定錯誤訊息)。 +- **Edge cases**: 列出特殊情況與預期行為(零、非常大值、重複、非整數、空輸入等)。 + + +----- + +檔案:`3three.py` 函式說明 + +1) `to_base3` + +- **函式簽名**: `def to_base3(n: int) -> str` +- **參數與回傳值**: + - 參數 `n`:非負整數,表示十進位數值。 + - 回傳:`n` 的三進位字串表示法,例如 `12 -> "110"`。 +- **輸入邊界**: + - 預期輸入為 `n >= 0`。 + - `n = 0` 會直接回傳 `"0"`。 + - 若傳入負數,函式目前不處理負號,會直接進入迴圈以外的邏輯限制,因此呼叫端應避免負數。 +- **例外處理**: + - 若 `n` 不是整數,`while n > 0` 與 `%`、`//` 運算可能造成 `TypeError`。 + - 目前函式未額外捕捉例外,需由呼叫端確保型別正確。 +- **Edge cases**: + - `n = 0`:回傳 `"0"`。 + - 非常大的整數:仍可正常轉換,但位數越多,輸出字串越長。 + - `n = 1`、`n = 2`:分別回傳 `"1"`、`"2"`。 + +2) `iterative_base3_digit_sum_steps` + +- **函式簽名**: `def iterative_base3_digit_sum_steps(n: int) -> tuple[list[str], int]` +- **參數與回傳值**: + - 參數 `n`:十進位整數。 + - 回傳:`(steps, result)`,其中 `steps` 是每一步轉三進位後加總的字串列表,`result` 是最後得到的個位數整數。 + - 副作用:不直接列印,但會回傳可供 GUI/CLI 顯示的步驟內容。 +- **輸入邊界**: + - 接受整數類型,建議 `n >= 0`。 + - `n = 0` 會直接得到結果 `0`。 + - `n < 10` 時不需要進入 while 迴圈,會直接回傳原值。 +- **例外處理**: + - 若 `n` 非整數,`to_base3` 與 `sum(int(d) for d in b3)` 可能引發 `TypeError`。 + - 目前函式未自行檢查負數或型別,呼叫端需先驗證輸入。 +- **Edge cases**: + - `n = 0`:回傳 `(['0 -> 0 -> sum: 0'], 0)`。 + - `0 < n < 10`:回傳空步驟列表與原始數字,例如 `n = 7` 時回傳 `([], 7)`。 + - `n >= 10`:會持續把十進位轉成三進位、加總位數,直到結果小於 10。 + - 非常大數值:迴圈次數通常不多,但每次轉換與字串處理會隨數值增大而增加成本。 + +3) `run_cli` + +- **函式簽名**: `def run_cli(data: str) -> None` +- **參數與回傳值**: + - 參數 `data`:從標準輸入取得的原始字串。 + - 無回傳值;會將每一步結果與最終結果列印到標準輸出。 +- **輸入邊界**: + - 預期輸入為可轉成整數的字串。 + - 空白字串或只包含空白時,不輸出任何內容。 + - 可接受前後空白,會先用 `strip()` 去除。 +- **例外處理**: + - 若無法轉成整數,會印出 `請輸入整數` 並結束。 + - 目前未捕捉其他例外,若內部函式失敗將直接中斷。 +- **Edge cases**: + - 空輸入:直接返回,不輸出任何內容。 + - 非整數字串:輸出 `請輸入整數`。 + - 合法整數字串:逐步輸出每一輪轉換結果與 `最終結果:X`。 + +4) `run_gui` + +- **函式簽名**: `def run_gui() -> None` +- **參數與回傳值**: + - 無參數。 + - 無回傳值;啟動 Tkinter 視窗,讓使用者輸入數字並按 Enter 或按鈕計算結果。 +- **輸入邊界**: + - 使用者於輸入框輸入的內容應為可轉成整數的字串。 + - 空白輸入會直接忽略,不顯示錯誤。 +- **例外處理**: + - 若無法載入 Tkinter,會印出 `無法載入 Tkinter,請使用命令列模式` 後返回。 + - 若使用者輸入非整數,會以 `messagebox.showerror('錯誤', '請輸入整數')` 顯示錯誤視窗。 +- **Edge cases**: + - 按 Enter 與按下「計算 (Enter)」按鈕會觸發相同邏輯。 + - 空輸入:不做任何事。 + - 合法輸入:清空文字區並顯示每一步與最終結果。 + +5) `main` + +- **函式簽名**: `def main() -> None` +- **參數與回傳值**: + - 無參數,根據標準輸入是否為 pipe/redirect 來決定 CLI 或 GUI 模式。 + - 無回傳值。 +- **輸入邊界**: + - 如果 `sys.stdin.isatty()` 為 `False`,代表有管線或重導輸入,會走 CLI 模式。 + - 如果 `sys.stdin.isatty()` 為 `True`,會開啟 GUI。 +- **例外處理**: + - `main` 本身不額外捕捉 `run_cli` 或 `run_gui` 內部的未處理例外。 + - 若 GUI 無法啟動,`run_gui` 會印出提示訊息而不拋錯。 +- **Edge cases**: + - 互動式執行:開啟視窗。 + - 管線輸入:直接執行 CLI 輸出。 + - 若執行環境沒有 Tkinter:GUI 模式會顯示提示,建議改用 CLI。 + +範例執行: + +```bash +echo "12" | python 3three.py +``` + +預期輸出(stdout): + +```text +12 -> 110 -> sum: 2 +最終結果:2 +``` + diff --git a/weeks/week-02/solutions/1112405053/AI_LOG_4.md b/weeks/week-02/solutions/1112405053/AI_LOG_4.md new file mode 100644 index 000000000..8a2bb3e5d --- /dev/null +++ b/weeks/week-02/solutions/1112405053/AI_LOG_4.md @@ -0,0 +1,137 @@ +函式設計說明 + +- **函式簽名**: `def name(params...) -> return_type` — 明確列出型別。 +- **參數與回傳值**: 每個參數的型別與語意;回傳值型別與結構說明。 +- **輸入邊界**: 明確允許的值域/型別(含最小/最大/是否含負數、空字串等)。 +- **例外處理**: 哪些錯誤會被拋出或被捕捉並如何回報(例:拋出 `ValueError`、回傳特定錯誤訊息)。 +- **Edge cases**: 列出特殊情況與預期行為(零、非常大值、重複、非整數、空輸入等)。 + + +----- + +檔案:`search_eval.py` 函式說明 + +1) `binary_search` + +- **函式簽名**: `def binary_search(array: list, k: int) -> tuple[int, int]` +- **參數與回傳值**: + - 參數 `array`:已升冪排序的整數列表。 + - 參數 `k`:搜尋目標整數。 + - 回傳:`(idx, cmp_count)`,其中 `idx` 是目標位置(找不到時為 -1),`cmp_count` 是執行的比較次數。 +- **輸入邊界**: + - `array` 必須為升冪排序的列表;若未排序,演算法無法正確收斂。 + - `k` 應為整數;若為其他型別,比較時會拋出 `TypeError`。 + - 陣列大小通常為 $N \approx 10^5$,以展示效能差異。 +- **例外處理**: + - 若 `array[mid]` 與 `k` 型別不相容(如一個是字串、一個是整數),比較運算會拋出 `TypeError`。 + - 目前函式未額外捕捉例外,呼叫端應確保陣列已排序且型別一致。 +- **Edge cases**: + - `k` 恰好是陣列首元素:執行 1 次比較即返回 `(0, 1)`。 + - `k` 恰好是陣列末元素:執行最多 $\log_2(N)$ 次比較,約 16-17 次(N=100000)。 + - `k` 不在陣列中:執行最多 $\log_2(N)$ 次比較後回傳 `(-1, cmp_count)`。 + - 空陣列:`high = -1` 使 `low <= high` 為假,直接返回 `(-1, 0)`。 + +2) `linear_search_cmp` + +- **函式簽名**: `def linear_search_cmp(array: list, k: int) -> tuple[int, int]` +- **參數與回傳值**: + - 參數 `array`:任意整數列表(無需排序)。 + - 參數 `k`:搜尋目標整數。 + - 回傳:`(idx, cmp_count)`,其中 `idx` 是首次找到的位置(找不到時為 -1),`cmp_count` 是執行的比較次數。 +- **輸入邊界**: + - `array` 可為任意列表,未排序亦可。 + - `k` 應為整數;型別不符時會在比較時拋錯。 + - 最壞情況下會走訪全陣列,執行 $N$ 次比較。 +- **例外處理**: + - 若 `array[i]` 與 `k` 型別不相容,比較時會拋出 `TypeError`。 + - 目前函式未額外捕捉例外。 +- **Edge cases**: + - `k` 在陣列首位:執行 1 次比較即返回 `(0, 1)`。 + - `k` 在陣列末位:執行 $N$ 次比較(最壞情況)。 + - `k` 不在陣列中:執行 $N$ 次比較後回傳 `(-1, N)`。 + - 陣列含多個相同元素:會回傳首個比對位置的索引。 + +3) `run_linear` + +- **函式簽名**: `def run_linear() -> None` +- **參數與回傳值**: + - 無參數(使用外層作用域的 `arr` 與 `target` 變數)。 + - 無回傳值;供 `timeit` 呼叫以測量耗時。 +- **輸入邊界**: + - 依賴外層的 `arr`(全域或上層作用域的升冪排序列表)與 `target`(搜尋目標)。 + - 若這些變數未定義或型別錯誤,會拋出 `NameError` 或 `TypeError`。 +- **例外處理**: + - `for` 迴圈中若 `arr` 的元素與 `target` 無法比較,會拋出 `TypeError`。 + - 目前函式未自行處理例外。 +- **Edge cases**: + - 若 `target` 在陣列首位,迴圈會立即 `break`,耗時極短。 + - 若 `target` 不在陣列中,迴圈會完整走訪全陣列,耗時最長。 + - 空陣列:迴圈不執行,函式直接返回。 + +4) `run_binary` + +- **函式簽名**: `def run_binary() -> None` +- **參數與回傳值**: + - 無參數(使用外層作用域的 `arr` 與 `target` 變數)。 + - 無回傳值;供 `timeit` 呼叫以測量耗時。 +- **輸入邊界**: + - 依賴外層的 `arr`(升冪排序列表)與 `target`(搜尋目標)。 + - 若 `arr` 未排序,二分搜尋會失效。 +- **例外處理**: + - 若 `arr[mid]` 與 `target` 型別不相容,比較時會拋出 `TypeError`。 + - 目前函式未額外捕捉例外。 +- **Edge cases**: + - 若 `target` 恰好位於中點,僅執行 1 次比較即 `break`。 + - 若 `target` 不在陣列中,會執行 $\log_2(N)$ 次迭代後自然終止。 + - 空陣列:`high = -1` 使初始條件 `low <= high` 為假,直接返回。 + + +----- + +檔案:`plot.py` 繪圖說明 + +**函式簽名**: 無獨立函式;此檔案為主程式直接繪製雷達圖。 + +**參數與回傳值**: +- 定義了 5 維度評估指標(labels)與對應的歸一化得分(0.0-1.0)。 +- 線性搜尋得分:`[0.1, 1.0, 1.0, 1.0, 0.1]` +- 二分搜尋得分:`[1.0, 0.8, 0.2, 0.6, 1.0]` +- 輸出:保存至 `assets/radar.png`(300 DPI)。 + +**輸入邊界**: +- `labels`:5 個維度的英文與中文標籤。 +- `linear_scores` 與 `binary_scores`:各 5 個浮點數,範圍 0.0-1.0。 +- `angles`:由 `np.linspace` 生成的極座標角度,自動計算無需手動輸入。 + +**例外處理**: +- 若 `matplotlib.use("Agg")` 未在導入前呼叫,在無 GUI 環境下可能產生警告或錯誤。 +- 若 `assets` 目錄不存在,`plt.savefig()` 會拋出 `FileNotFoundError`;需先建立目錄。 +- 若 matplotlib 或 numpy 未安裝,會拋出 `ImportError`。 + +**Edge cases**: +- 維度數量不一致(labels 與 scores 長度不同)會導致繪製錯誤;目前程式假設維度數量一致。 +- 得分超過 1.0 或小於 0.0 會超出繪圖範圍,需手動正規化。 +- 空 `labels` 或空得分列表會導致無法繪製。 + +**驗收與成果**: + +執行結果: +``` +FOUND 152 cmp=16 +linear: 0.000242 s +binary: 0.000138 s +binary faster +Radar chart generated successfully at assets/radar.png +``` + +**多維權衡解讀**: + +| 維度 | 線性搜尋 | 二分搜尋 | 勝者 | +|------|--------|--------|------| +| 大 N 速度 | 0.1 | 1.0 | 二分 ✓ | +| 小 N 速度 | 1.0 | 0.8 | 線性 ✓ | +| 免預先排序 | 1.0 | 0.2 | 線性 ✓ | +| 實作簡易度 | 1.0 | 0.6 | 線性 ✓ | +| 最壞情況比較次數 | 0.1 | 1.0 | 二分 ✓ | + +**結論**:二分搜尋在大規模資料與最壞情況下具有壓倒性優勢($O(\log n)$),但線性搜尋在不需預先排序與實作簡易度上更具靈活性。實務應用中應根據資料規模、是否已排序、開發時程等條件進行權衡。