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
83 changes: 83 additions & 0 deletions weeks/week-18/solutions/1114405055/AI_LOG.md
Original file line number Diff line number Diff line change
@@ -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, !
```
34 changes: 34 additions & 0 deletions weeks/week-18/solutions/1114405055/caesar_cipher.py
Original file line number Diff line number Diff line change
@@ -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()
72 changes: 72 additions & 0 deletions weeks/week-18/solutions/1114405055/caesar_cipher_ai.py
Original file line number Diff line number Diff line change
@@ -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()
73 changes: 73 additions & 0 deletions weeks/week-18/solutions/1114405055/test_caesar_cipher.py
Original file line number Diff line number Diff line change
@@ -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"]
18 changes: 18 additions & 0 deletions weeks/week-18/solutions/1114405055/test_log.txt
Original file line number Diff line number Diff line change
@@ -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 ==============================
22 changes: 22 additions & 0 deletions weeks/week-18/solutions/1114405055/test_log_red.txt
Original file line number Diff line number Diff line change
@@ -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 <module>
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 ===============================