From be3e563e286b8944d401e59137859120ed498c00 Mon Sep 17 00:00:00 2001 From: 1114405019 Date: Mon, 22 Jun 2026 19:57:12 +0800 Subject: [PATCH 1/2] test(week18-1114405019): add digital root tests (red light) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_red.txt 紀錄此時跑測試的結果:因尚未實作 digital_root.py, import 失敗(ModuleNotFoundError),確認測試案例是先寫好且為紅燈。 --- .../solutions/1114405019/test_digital_root.py | 59 +++++++++++++++++++ .../week-18/solutions/1114405019/test_red.txt | 19 ++++++ 2 files changed, 78 insertions(+) create mode 100644 weeks/week-18/solutions/1114405019/test_digital_root.py create mode 100644 weeks/week-18/solutions/1114405019/test_red.txt diff --git a/weeks/week-18/solutions/1114405019/test_digital_root.py b/weeks/week-18/solutions/1114405019/test_digital_root.py new file mode 100644 index 000000000..46c14ddf7 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/test_digital_root.py @@ -0,0 +1,59 @@ +import unittest + +from digital_root import digit_sum_in_base, digital_root + + +class TestDigitSumInBase(unittest.TestCase): + def test_single_round_conversion(self): + # 8 的六進位是 12,1+2=3:驗證「轉進位+單輪相加」本身算得對 + self.assertEqual(digit_sum_in_base(8, 6), 3) + + def test_result_can_still_be_multi_digit(self): + # 63 的六進位是 143,1+4+3=8:8 在六進位仍是兩位數(12), + # 這個中間值是「需要再轉一輪才會收斂」的關鍵,不能漏掉 + self.assertEqual(digit_sum_in_base(63, 6), 8) + + def test_zero_has_no_digits_to_sum(self): + self.assertEqual(digit_sum_in_base(0, 6), 0) + + +class TestDigitalRoot(unittest.TestCase): + def test_zero_is_fixed_to_zero_by_spec(self): + # 題目明文規定 0 的數字根固定為 0,不是公式算出來的結果 + self.assertEqual(digital_root(0, 6), 0) + + def test_value_less_than_base_needs_no_iteration(self): + # 4 < 6,在六進位下本身就是一位數,不需要任何累加迭代 + self.assertEqual(digital_root(4, 6), 4) + + def test_converges_after_one_round(self): + # 8 -> 12(六進位) -> 1+2=3,一輪迭代即收斂 + self.assertEqual(digital_root(8, 6), 3) + + def test_converges_after_two_rounds(self): + # 63 -> 143(六進位) -> 8 -> 12(六進位) -> 3 + # 第一輪相加後仍是兩位數,必須再轉一次進位再加一次, + # 這是最容易漏掉的遞迴/迴圈邊界 + self.assertEqual(digital_root(63, 6), 3) + + def test_large_value_terminates_with_valid_single_digit(self): + # x 接近 10^9 時,確認迴圈會結束且收斂到合法的一位數範圍, + # 避免因為迴圈邊界寫錯造成死迴圈或效能爆炸 + root = digital_root(10**9, 6) + self.assertGreaterEqual(root, 0) + self.assertLess(root, 6) + + def test_base16_single_round(self): + # 16 的十六進位是 "10",1+0=1,一位數即收斂; + # 確認邏輯沒有寫死成只服務小 base + self.assertEqual(digital_root(16, 16), 1) + + def test_base16_digit_can_exceed_nine(self): + # 255 的十六進位是 FF(十進位數字 15,15),15+15=30, + # 30 的十六進位是 "1E"(1,14),1+14=15,15<16 收斂; + # 驗證進位數字大於 9 時仍以十進位數值相加,不是只處理 0-9 字元 + self.assertEqual(digital_root(255, 16), 15) + + +if __name__ == "__main__": + unittest.main() diff --git a/weeks/week-18/solutions/1114405019/test_red.txt b/weeks/week-18/solutions/1114405019/test_red.txt new file mode 100644 index 000000000..bcae8feaa --- /dev/null +++ b/weeks/week-18/solutions/1114405019/test_red.txt @@ -0,0 +1,19 @@ +test_digital_root (unittest.loader._FailedTest.test_digital_root) ... ERROR + +====================================================================== +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 "C:\Users\yiteng\AppData\Local\Programs\Python\Python311\Lib\unittest\loader.py", line 162, in loadTestsFromName + module = __import__(module_name) + ^^^^^^^^^^^^^^^^^^^^^^^ + File "C:\Users\yiteng\Downloads\0622-3\2026-python\weeks\week-18\solutions\1114405019\test_digital_root.py", line 3, in + from digital_root import digit_sum_in_base, digital_root +ModuleNotFoundError: No module named 'digital_root' + + +---------------------------------------------------------------------- +Ran 1 test in 0.000s + +FAILED (errors=1) From c87630fbd6f8096eb0b7582a758ea3ef18dc0332 Mon Sep 17 00:00:00 2001 From: 1114405019 Date: Mon, 22 Jun 2026 19:57:32 +0800 Subject: [PATCH 2/2] feat(week18-1114405019): implement digital root in base 6 (green light) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增手打版本 digital_root.py 與 AI 教的好記版 digital_root-easy.py, 讓上一個 commit 的測試全數轉綠燈(test_green.txt),並用題目 Sample I/O 驗證兩版本輸出一致(sample_io.txt / sample_io_easy.txt)。 補上 README.md 與 AI_LOG.md 說明本次提交內容與 AI 使用過程。 --- weeks/week-18/solutions/1114405019/AI_LOG.md | 40 +++++++++++++++++++ weeks/week-18/solutions/1114405019/README.md | 24 +++++++++++ .../solutions/1114405019/digital_root-easy.py | 40 +++++++++++++++++++ .../solutions/1114405019/digital_root.py | 38 ++++++++++++++++++ .../solutions/1114405019/sample_io.txt | 3 ++ .../solutions/1114405019/sample_io_easy.txt | 3 ++ .../solutions/1114405019/test_green.txt | 15 +++++++ 7 files changed, 163 insertions(+) create mode 100644 weeks/week-18/solutions/1114405019/AI_LOG.md create mode 100644 weeks/week-18/solutions/1114405019/README.md create mode 100644 weeks/week-18/solutions/1114405019/digital_root-easy.py create mode 100644 weeks/week-18/solutions/1114405019/digital_root.py create mode 100644 weeks/week-18/solutions/1114405019/sample_io.txt create mode 100644 weeks/week-18/solutions/1114405019/sample_io_easy.txt create mode 100644 weeks/week-18/solutions/1114405019/test_green.txt diff --git a/weeks/week-18/solutions/1114405019/AI_LOG.md b/weeks/week-18/solutions/1114405019/AI_LOG.md new file mode 100644 index 000000000..983698198 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/AI_LOG.md @@ -0,0 +1,40 @@ +# AI 使用紀錄:第三題(任意進位的數字根) + +依 week-18 README 的「AI 使用方式」五步驟,實際操作紀錄如下。 + +## 1. 讀題目設計 unit test,加繁中註解 + +先把題目(base 依學號查表得 6、需多輪收斂、x=0 特例、base 可能是 16)整理成假設與函式簽名提案,經我確認後才寫測試: + +- 產出 `test_digital_root.py`,針對 `digit_sum_in_base` 與 `digital_root` 兩個函式設計測試,涵蓋: + - 0 的特例(規定值,非公式算出) + - x < base 的單一位數(免迭代) + - 63 案例需要兩輪才收斂(最容易漏掉的邊界) + - 大數(10^9)確認不死迴圈 + - base=16 時數字大於 9 仍以十進位數值相加 +- 在實作之前先跑一次測試,確認是紅燈(`ModuleNotFoundError`),紀錄於 `test_red.txt`。 + +## 2. 寫程式並跑完測試,保留測試紀錄 + +確認測試案例後才寫 `digital_root.py`(拆成 `digit_sum_in_base` 轉進位+相加、`digital_root` 收斂迴圈、`main` 用 EOF 讀取輸入)。跑測試轉綠燈,紀錄於 `test_green.txt`;另用題目 Sample I/O(0/8/63)驗證實際輸出,紀錄於 `sample_io.txt`。 + +## 3. 加上繁體中文的註解說明 + +為 `digital_root.py` 補上逐行的繁體中文註解,說明 `x % base` / `x // base` 的短除法概念,以及收斂迴圈的終止條件。 + +## 4. 更簡單、更好記的版本(`-easy`) + +請 AI 提供不拆 helper function、單一函式內用巢狀迴圈完成的「好記版」,產出 `digital_root-easy.py`,並用同一組 Sample I/O 驗證輸出與手打版一致(`sample_io_easy.txt`)。 + +## 5. 加上繁體中文的詳細註解說明 + +為 `digital_root-easy.py` 補上詳細的繁體中文註解,包含口訣式的邏輯說明(「只要還是兩位數以上就繼續加總,直到變成一位數」)及 x=0 為何不需要特例判斷。 + +## 使用的 AI 工具 + +Claude Code(claude-sonnet-4-6)。 + +## 人工確認事項 + +- 函式簽名提案、測試案例內容、Sample I/O 預期輸出(base=6 下 0/8/63 → 0/3/3)均由我手動確認後才進入下一步。 +- 所有測試與 Sample I/O 結果均實際執行驗證,未盲目採信 AI 輸出。 diff --git a/weeks/week-18/solutions/1114405019/README.md b/weeks/week-18/solutions/1114405019/README.md new file mode 100644 index 000000000..6478ab372 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/README.md @@ -0,0 +1,24 @@ +# 第三題:任意進位的數字根(base=6) + +依學號末兩碼 19,個位 9 查對照表得 `base=6`。 + +## 檔案說明 + +| 檔案 | 說明 | +| --- | --- | +| `digital_root-easy.py` | AI 教的簡單版本:單一函式 `digital_root_easy`,不拆 helper function,邏輯較好記,附詳細繁體中文註解 | +| `digital_root.py` | 手打版本:拆成 `digit_sum_in_base`(單輪轉進位+相加)與 `digital_root`(收斂迴圈)兩個函式 | +| `test_digital_root.py` | 針對手打版本的 unittest 測試,涵蓋 0、單輪收斂、雙輪收斂、大數、base=16 等邊界 | +| `test_red.txt` | 實作前跑測試的紀錄(紅燈,`ModuleNotFoundError`) | +| `test_green.txt` | 實作後跑測試的紀錄(綠燈,10 個測試全過) | +| `sample_io.txt` | 手打版本對題目 Sample I/O(輸入 0/8/63)的實際輸出 | +| `sample_io_easy.txt` | 簡單版本對同一組 Sample I/O 的實際輸出(用來確認兩版本結果一致) | +| `AI_LOG.md` | AI 使用紀錄,依 week-18 README 的五步驟記錄實際操作過程與人工確認點 | + +## 執行方式 + +``` +echo -e "0\n8\n63" | python digital_root.py +echo -e "0\n8\n63" | python digital_root-easy.py +python -m unittest test_digital_root.py -v +``` diff --git a/weeks/week-18/solutions/1114405019/digital_root-easy.py b/weeks/week-18/solutions/1114405019/digital_root-easy.py new file mode 100644 index 000000000..bbb010ff1 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/digital_root-easy.py @@ -0,0 +1,40 @@ +import sys + +BASE = 6 # 學號末兩碼 19,個位 9 查對照表得 base=6 + + +def digital_root_easy(x: int, base: int) -> int: + """ + 數字根的「好記版」寫法:不拆成兩個函式,直接用一個迴圈做到底。 + + 口訣:只要 x 在 base 進位下還是兩位數以上(也就是 x >= base), + 就把它的每一位數字加起來,變成新的 x,再檢查一次。 + 一直重複,直到 x 變成一位數(x < base)為止。 + + x=0 的情況:0 < base 一定成立,迴圈完全不會執行,直接回傳 0, + 剛好符合「0 的數字根固定是 0」的規定,不需要額外特例判斷。 + """ + while x >= base: + # 這裡是「短除法」的概念:每次用 x % base 拿到最右邊一位數字, + # 再用 x // base 把這一位丟掉,繼續處理剩下的位數, + # 直到 x 被除到 0,總和就是這一輪所有位數加起來的結果。 + digit_sum = 0 + while x > 0: + digit_sum += x % base + x //= base + x = digit_sum # 用這一輪算出的總和取代 x,回到外層迴圈再檢查一次 + return x + + +def main() -> None: + # 跟手打版一樣讀到 EOF 結束,不是用 0 當終止值 + for line in sys.stdin: + line = line.strip() + if not line: + continue + x = int(line) + print(digital_root_easy(x, BASE)) + + +if __name__ == "__main__": + main() diff --git a/weeks/week-18/solutions/1114405019/digital_root.py b/weeks/week-18/solutions/1114405019/digital_root.py new file mode 100644 index 000000000..7e4d1c5c7 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/digital_root.py @@ -0,0 +1,38 @@ +import sys + +BASE = 6 # 學號末兩碼 19,個位 9 查對照表得 base=6 + + +def digit_sum_in_base(x: int, base: int) -> int: + """將十進位非負整數 x 換算成 base 進位,回傳各位數字相加的十進位總和""" + total = 0 + while x > 0: + # x % base:取出 base 進位下最右邊那一位的數字(仍以十進位數值表示) + # x // base:把這一位去掉,準備取下一位,相當於短除法不斷往左移 + total += x % base + x //= base + return total + + +def digital_root(x: int, base: int) -> int: + """重複呼叫 digit_sum_in_base,直到結果在 base 進位下為一位數,回傳數字根""" + # x < base 代表 x 在 base 進位下只剩一位數,這就是收斂的終止條件; + # 例如 63 -> digit_sum_in_base 後變成 8,8 在六進位仍是兩位數(12), + # 所以還要再跑一輪,直到結果小於 base 為止 + while x >= base: + x = digit_sum_in_base(x, base) + return x + + +def main() -> None: + # 讀到 EOF 結束(跟第一題用 n=0 終止不同,這題沒有終止值) + for line in sys.stdin: + line = line.strip() + if not line: + continue + x = int(line) + print(digital_root(x, BASE)) + + +if __name__ == "__main__": + main() diff --git a/weeks/week-18/solutions/1114405019/sample_io.txt b/weeks/week-18/solutions/1114405019/sample_io.txt new file mode 100644 index 000000000..34f748e1c --- /dev/null +++ b/weeks/week-18/solutions/1114405019/sample_io.txt @@ -0,0 +1,3 @@ +0 +3 +3 diff --git a/weeks/week-18/solutions/1114405019/sample_io_easy.txt b/weeks/week-18/solutions/1114405019/sample_io_easy.txt new file mode 100644 index 000000000..34f748e1c --- /dev/null +++ b/weeks/week-18/solutions/1114405019/sample_io_easy.txt @@ -0,0 +1,3 @@ +0 +3 +3 diff --git a/weeks/week-18/solutions/1114405019/test_green.txt b/weeks/week-18/solutions/1114405019/test_green.txt new file mode 100644 index 000000000..5bfb545b9 --- /dev/null +++ b/weeks/week-18/solutions/1114405019/test_green.txt @@ -0,0 +1,15 @@ +test_result_can_still_be_multi_digit (test_digital_root.TestDigitSumInBase.test_result_can_still_be_multi_digit) ... ok +test_single_round_conversion (test_digital_root.TestDigitSumInBase.test_single_round_conversion) ... ok +test_zero_has_no_digits_to_sum (test_digital_root.TestDigitSumInBase.test_zero_has_no_digits_to_sum) ... ok +test_base16_digit_can_exceed_nine (test_digital_root.TestDigitalRoot.test_base16_digit_can_exceed_nine) ... ok +test_base16_single_round (test_digital_root.TestDigitalRoot.test_base16_single_round) ... ok +test_converges_after_one_round (test_digital_root.TestDigitalRoot.test_converges_after_one_round) ... ok +test_converges_after_two_rounds (test_digital_root.TestDigitalRoot.test_converges_after_two_rounds) ... ok +test_large_value_terminates_with_valid_single_digit (test_digital_root.TestDigitalRoot.test_large_value_terminates_with_valid_single_digit) ... ok +test_value_less_than_base_needs_no_iteration (test_digital_root.TestDigitalRoot.test_value_less_than_base_needs_no_iteration) ... ok +test_zero_is_fixed_to_zero_by_spec (test_digital_root.TestDigitalRoot.test_zero_is_fixed_to_zero_by_spec) ... ok + +---------------------------------------------------------------------- +Ran 10 tests in 0.000s + +OK