From ad9ad63e29d23a1c8f04c8194fcad453892f31fe Mon Sep 17 00:00:00 2001 From: yuchi-create Date: Mon, 22 Jun 2026 20:10:04 +0800 Subject: [PATCH 1/2] Submit week-18 - 1114405055 - Caesar Cipher Co-Authored-By: Claude Sonnet 4.6 --- weeks/week-18/solutions/1114405055/AI_LOG.md | 83 +++++++++++++++++++ .../solutions/1114405055/caesar_cipher.py | 34 ++++++++ .../solutions/1114405055/caesar_cipher_ai.py | 72 ++++++++++++++++ .../1114405055/test_caesar_cipher.py | 73 ++++++++++++++++ .../week-18/solutions/1114405055/test_log.txt | 18 ++++ 5 files changed, 280 insertions(+) create mode 100644 weeks/week-18/solutions/1114405055/AI_LOG.md create mode 100644 weeks/week-18/solutions/1114405055/caesar_cipher.py create mode 100644 weeks/week-18/solutions/1114405055/caesar_cipher_ai.py create mode 100644 weeks/week-18/solutions/1114405055/test_caesar_cipher.py create mode 100644 weeks/week-18/solutions/1114405055/test_log.txt diff --git a/weeks/week-18/solutions/1114405055/AI_LOG.md b/weeks/week-18/solutions/1114405055/AI_LOG.md new file mode 100644 index 000000000..1f7cd9cfb --- /dev/null +++ b/weeks/week-18/solutions/1114405055/AI_LOG.md @@ -0,0 +1,83 @@ +# 第二題 凱撒密碼 (Caesar Cipher) + +## 學號專屬參數 +- 位移量 SHIFT = 6 + +## 開工前五項檢查表 + +### ① 函式簽名 (Function Signature) +- 單字元位移與整行處理拆成兩個函式,方便針對「邊界循環」這種容易出錯的點單獨測試,不用每次都組一整行字串: + - `shift_char(ch: str, shift: int) -> str`:只處理一個字元,大寫在 A-Z、小寫在 a-z 內循環,非英文字母原樣回傳。 + - `encrypt_line(line: str, shift: int) -> str`:對整行逐字元套用 `shift_char` 後組回字串,不含任何 I/O。 +- `main()` 只負責讀 `sys.stdin` 並逐行印出結果,不含任何加密邏輯,確保核心邏輯與 I/O 迴圈完全分離、各自可測。 + +### ② 輸入邊界 +- 輸入為多行字串,每行長度 ≤ 1000,可能包含空白與標點。 +- 終止條件是 **EOF(檔案結尾)**,不是讀到某個特定數值才停(跟第一題的 n=0 不同),用 `for line in sys.stdin` 自然處理,不需要額外判斷終止值。 +- 空白行(長度 0)也是合法輸入,必須能正常通過,不可拋例外或被跳過。 + +### ③ 例外處理 +- 非英文字母字元(空白、數字、標點)一律原樣保留,不可被誤位移;用 `'a' <= ch <= 'z'` / `'A' <= ch <= 'Z'` 明確判斷範圍,不在範圍內就直接回傳原字元。 +- 字母循環必須用 `% 26` 處理,需特別驗證「位移量加上去之後超過 z/Z」時是否正確繞回字母表開頭(例如 SHIFT=6 時 `u→a`、`U→A`),不能只驗證中間值。 +- 讀取每一行時要去掉換行符 `\n` 再處理,否則會把換行符也一起印出造成多印一行空白。 + +### ④ Edge Case 設計重點 +- 空白行(長度為 0)→ 輸出也是空白行。 +- 整行都是非字母字元(純數字、純標點、純空白)→ 原樣輸出。 +- 字母在循環邊界上:SHIFT=6 時 `u→a`、`U→A`(小寫/大寫各驗證一次,確認繞回開頭的邏輯正確)。 +- 混合大小寫、數字、標點同一行(例如 `Ab3, c!`),驗證三種字元類型互不干擾。 +- 多行輸入要驗證 EOF 結束迴圈而非靠終止標記,輸出行數要跟輸入行數一致。 + +### ⑤ 驗收標準 +- 兩組學校 Sample Input 套用 SHIFT=6 後,輸出必須與下方「紅燈測試案例」逐字元一致。 +- 邊界案例(循環繞回、空白行、純非字母行、混合字元)需正確處理,不可拋出例外或輸出格式錯誤。 +- `main()` 的讀取迴圈要能正確在 EOF 時結束,且不依賴任何特定終止數值。 + +--- + +## 🔴 紅燈測試案例(Red Light Test Cases) + +### 測資 1(學校 Sample,SHIFT=6) +**Input:** +``` +Hello, NPU! +``` +**處理過程:** +- 逐字元位移:`H→N, e→k, l→r, l→r, o→u`,逗號、空白、驚嘆號不變 +- 組合:`Nkrru, TVA!`(N→T、P→V、U→A 同樣依循環規則位移) + +**Expected Output:** +``` +Nkrru, TVA! +``` + +### 測資 2(學校 Sample,SHIFT=6) +**Input:** +``` +abc XYZ +``` +**處理過程:** +- 逐字元位移:`a→g, b→h, c→i` +- 大寫部分:`X→D, Y→E, Z→F`(繞回字母表開頭) + +**Expected Output:** +``` +ghi DEF +``` + +### 🧩 邊界案例(Edge Case,自行設計) +**設計動機:** 同時驗證「小寫循環繞回開頭」、「大寫循環繞回開頭」、「非字母字元不變」、「空白行」四個風險點,避免只測中間值就誤以為邏輯正確。 + +**Input:** +``` +uU3, ! +``` +**處理過程:** +- `u`(+6) 超過 `z`,繞回開頭 → `a` +- `U`(+6) 超過 `Z`,繞回開頭 → `A` +- `3`、`,`、空白、`!` 皆非英文字母,原樣保留 + +**Expected Output:** +``` +aA3, ! +``` diff --git a/weeks/week-18/solutions/1114405055/caesar_cipher.py b/weeks/week-18/solutions/1114405055/caesar_cipher.py new file mode 100644 index 000000000..088b795ce --- /dev/null +++ b/weeks/week-18/solutions/1114405055/caesar_cipher.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +凱撒密碼(Caesar Cipher) +學號 1114405055 的參數:SHIFT = 6 +輸入:多行字串,讀到 EOF 為止 +輸出:每行對應的加密結果 +""" +import sys + +SHIFT = 6 + + +def shift_char(ch: str, shift: int) -> str: + """單一字元位移:大寫在 A-Z、小寫在 a-z 內循環,非英文字母原樣回傳""" + if 'a' <= ch <= 'z': + return chr((ord(ch) - ord('a') + shift) % 26 + ord('a')) + if 'A' <= ch <= 'Z': + return chr((ord(ch) - ord('A') + shift) % 26 + ord('A')) + return ch + + +def encrypt_line(line: str, shift: int) -> str: + """對整行逐字元套用 shift_char 後組回字串""" + return ''.join(shift_char(ch, shift) for ch in line) + + +def main() -> None: + """讀 stdin 直到 EOF,每行加密後印出""" + for line in sys.stdin: + print(encrypt_line(line.rstrip('\n'), SHIFT)) + + +if __name__ == '__main__': + main() diff --git a/weeks/week-18/solutions/1114405055/caesar_cipher_ai.py b/weeks/week-18/solutions/1114405055/caesar_cipher_ai.py new file mode 100644 index 000000000..011674543 --- /dev/null +++ b/weeks/week-18/solutions/1114405055/caesar_cipher_ai.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +""" +凱撒密碼(Caesar Cipher)- AI 教學版(簡單好記寫法) +學號 1114405055 的參數:SHIFT = 6 + +這一版的核心想法: + 不用每個字元自己算 ord()/chr(),而是先把「整套 26 個大寫字母」 + 和「整套 26 個小寫字母」各自做好「位移後對照表」(translate table), + 之後不管多長的字串,呼叫一次 str.translate() 就能整行轉換完畢。 + 這種「先建表、再查表」的寫法比逐字元手算 ord/chr 更不容易出錯, + 也比較好記:只要記得「位移後的字母表」要怎麼拼出來就好。 +""" +import sys +import string + +SHIFT = 6 + + +def _build_shift_table(shift: int) -> dict: + """ + 建立位移對照表(給 str.translate 用)。 + + 做法: + 1. string.ascii_uppercase 是 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 把它往左切掉 shift 個字母,再接到後面,就變成「位移後的字母順序」。 + 例如 shift=6 時:原本 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 切成 "ABCDEF" + "GHIJKLMNOPQRSTUVWXYZ", + 重組成 "GHIJKLMNOPQRSTUVWXYZABCDEF"。 + 也就是說「原本排第 0 位的 A,對照到新表第 0 位的 G」, + 剛好就是 A 位移 6 之後變成 G,邏輯一致。 + 2. 小寫字母用同樣方法處理 string.ascii_lowercase。 + 3. str.maketrans(原字串, 新字串) 會回傳一個對照表, + 之後 line.translate(表) 就會把 line 裡每個出現在「原字串」中的字元, + 換成「新字串」中對應位置的字元;不在表中的字元(標點、數字、空白) + 完全不受影響,自動原樣保留,不用額外寫 if 判斷。 + """ + upper = string.ascii_uppercase + lower = string.ascii_lowercase + + shifted_upper = upper[shift:] + upper[:shift] + shifted_lower = lower[shift:] + lower[:shift] + + return str.maketrans(upper + lower, shifted_upper + shifted_lower) + + +# 程式一啟動就把對照表建好,之後每一行都重複使用同一張表,不用重算 +_SHIFT_TABLE = _build_shift_table(SHIFT) + + +def encrypt_line(line: str) -> str: + """ + 用事先建好的對照表,把整行字串一次轉換完成。 + 非英文字母字元(空白、數字、標點)因為不在對照表裡, + translate() 會自動保留原樣,不需要額外處理。 + """ + return line.translate(_SHIFT_TABLE) + + +def main() -> None: + """ + 讀取輸入直到 EOF(檔案結尾)為止: + for line in sys.stdin 會一直讀下一行, + 直到真的沒有輸入了(EOF)才自然結束迴圈, + 跟「讀到某個特定數值就停止」的寫法不同,這裡完全不需要判斷終止條件。 + """ + for line in sys.stdin: + # line 結尾會帶有換行符 '\n',要先去掉,避免印出時多一行空白 + print(encrypt_line(line.rstrip('\n'))) + + +if __name__ == '__main__': + main() diff --git a/weeks/week-18/solutions/1114405055/test_caesar_cipher.py b/weeks/week-18/solutions/1114405055/test_caesar_cipher.py new file mode 100644 index 000000000..e0de8670c --- /dev/null +++ b/weeks/week-18/solutions/1114405055/test_caesar_cipher.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +""" +凱撒密碼測試(SHIFT = 6) +測試對象:caesar_cipher.py 內的 shift_char / encrypt_line / main +""" +import io +import sys + +from caesar_cipher import shift_char, encrypt_line, main, SHIFT + + +def test_full_line_sample1(): + # 依使用者列出的逐字元位移規則:H->N, e->k, l->r, l->r, o->u;逗號/空白/驚嘆號不變 + # 注意:使用者原文手寫的最終結果字串是 "Nkeru",與其自己列的逐字元規則 N-k-r-r-u 不符, + # 視為手寫筆誤,測試以逐字元規則推出的 "Nkrru" 為準 + assert encrypt_line("Hello, NPU!", SHIFT) == "Nkrru, TVA!" + + +def test_full_line_sample2(): + # 第二組驗算範例:abc->ghi, XYZ->DEF(含大寫循環繞回開頭) + assert encrypt_line("abc XYZ", SHIFT) == "ghi DEF" + + +def test_lowercase_wraps_around_boundary(): + # 'u' + 6 會超過 'z',必須繞回字母表開頭變成 'a', + # 這裡專門測循環邊界,不能只測中間值(例如 a->g 那種不會碰到邊界的情況) + assert shift_char('u', SHIFT) == 'a' + + +def test_uppercase_wraps_around_boundary(): + # 'U' + 6 同理應繞回 'A',驗證大寫循環邏輯跟小寫一致 + assert shift_char('U', SHIFT) == 'A' + + +def test_non_alpha_chars_unchanged(): + # 標點、數字、空白都不是英文字母,規則要求原樣保留,不能被誤位移 + assert shift_char('!', SHIFT) == '!' + assert shift_char('3', SHIFT) == '3' + assert shift_char(' ', SHIFT) == ' ' + + +def test_empty_line_stays_empty(): + # 長度為 0 的字串本身沒有任何字元可位移,預期輸出仍是空字串 + assert encrypt_line("", SHIFT) == "" + + +def test_line_with_only_non_alpha_chars(): + # 整行都沒有英文字母時,逐字元位移規則對每個字元都不生效,輸出應與輸入相同 + assert encrypt_line("123, !!?", SHIFT) == "123, !!?" + + +def test_mixed_case_digits_and_punctuation_in_one_line(): + # 同一行混合大寫、小寫、數字、標點,驗證三種字元各自的處理規則 + # 互不干擾:A->G, b->h, 3 不變(非字母), c->i, ! 不變(非字母) + assert encrypt_line("Ab3, c!", SHIFT) == "Gh3, i!" + + +def test_main_reads_until_eof_not_until_sentinel_value(): + # 重點:這題的輸入結束條件是 EOF,不是像第一題那樣讀到某個終止值(如 n=0)才停。 + # 用兩行輸入(無終止標記)模擬 stdin,確認 main 會把兩行都處理完才結束, + # 且輸出行數與輸入行數一致。 + fake_stdin = io.StringIO("Hello, NPU!\nabc XYZ\n") + fake_stdout = io.StringIO() + + old_stdin, old_stdout = sys.stdin, sys.stdout + sys.stdin, sys.stdout = fake_stdin, fake_stdout + try: + main() + finally: + sys.stdin, sys.stdout = old_stdin, old_stdout + + output_lines = fake_stdout.getvalue().splitlines() + assert output_lines == ["Nkrru, TVA!", "ghi DEF"] diff --git a/weeks/week-18/solutions/1114405055/test_log.txt b/weeks/week-18/solutions/1114405055/test_log.txt new file mode 100644 index 000000000..3da7ad905 --- /dev/null +++ b/weeks/week-18/solutions/1114405055/test_log.txt @@ -0,0 +1,18 @@ +============================= test session starts ============================= +platform win32 -- Python 3.13.9, pytest-8.4.2, pluggy-1.5.0 -- C:\Users\User\anaconda3\python.exe +cachedir: .pytest_cache +rootdir: C:\Users\User\Desktop\0622-2\2026-python\weeks\week-18\solutions\1114405055 +plugins: anyio-4.10.0 +collecting ... collected 9 items + +test_caesar_cipher.py::test_full_line_sample1 PASSED [ 11%] +test_caesar_cipher.py::test_full_line_sample2 PASSED [ 22%] +test_caesar_cipher.py::test_lowercase_wraps_around_boundary PASSED [ 33%] +test_caesar_cipher.py::test_uppercase_wraps_around_boundary PASSED [ 44%] +test_caesar_cipher.py::test_non_alpha_chars_unchanged PASSED [ 55%] +test_caesar_cipher.py::test_empty_line_stays_empty PASSED [ 66%] +test_caesar_cipher.py::test_line_with_only_non_alpha_chars PASSED [ 77%] +test_caesar_cipher.py::test_mixed_case_digits_and_punctuation_in_one_line PASSED [ 88%] +test_caesar_cipher.py::test_main_reads_until_eof_not_until_sentinel_value PASSED [100%] + +============================== 9 passed in 0.02s ============================== From 569ead965f625cf518767b3142c0e434c2b2a44e Mon Sep 17 00:00:00 2001 From: yuchi-create Date: Mon, 22 Jun 2026 20:16:24 +0800 Subject: [PATCH 2/2] Add red-light test log for Caesar Cipher (1114405055) Captures the failing test run before caesar_cipher.py existed, as evidence of the TDD red-green flow. Co-Authored-By: Claude Sonnet 4.6 --- .../solutions/1114405055/test_log_red.txt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 weeks/week-18/solutions/1114405055/test_log_red.txt diff --git a/weeks/week-18/solutions/1114405055/test_log_red.txt b/weeks/week-18/solutions/1114405055/test_log_red.txt new file mode 100644 index 000000000..5ad1ecd97 --- /dev/null +++ b/weeks/week-18/solutions/1114405055/test_log_red.txt @@ -0,0 +1,22 @@ +============================= test session starts ============================= +platform win32 -- Python 3.13.9, pytest-8.4.2, pluggy-1.5.0 -- C:\Users\User\anaconda3\python.exe +cachedir: .pytest_cache +rootdir: C:\Users\User\Desktop\0622-2\2026-python\weeks\week-18\solutions\1114405055 +plugins: anyio-4.10.0 +collecting ... collected 0 items / 1 error + +=================================== ERRORS ==================================== +___________________ ERROR collecting test_caesar_cipher.py ____________________ +ImportError while importing test module 'C:\Users\User\Desktop\0622-2\2026-python\weeks\week-18\solutions\1114405055\test_caesar_cipher.py'. +Hint: make sure your test modules/packages have valid Python names. +Traceback: +C:\Users\User\anaconda3\Lib\importlib\__init__.py:88: in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +test_caesar_cipher.py:9: in + from caesar_cipher import shift_char, encrypt_line, main, SHIFT +E ModuleNotFoundError: No module named 'caesar_cipher' +=========================== short test summary info =========================== +ERROR test_caesar_cipher.py +!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!! +============================== 1 error in 0.10s ===============================