diff --git a/weeks/week-18/solutions/1114405050/q1/AI_LOG.md b/weeks/week-18/solutions/1114405050/q1/AI_LOG.md new file mode 100644 index 000000000..20ca5f487 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q1/AI_LOG.md @@ -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` diff --git a/weeks/week-18/solutions/1114405050/q1/solution.py b/weeks/week-18/solutions/1114405050/q1/solution.py new file mode 100644 index 000000000..037c193f0 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q1/solution.py @@ -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 diff --git a/weeks/week-18/solutions/1114405050/q1/test_solution.py b/weeks/week-18/solutions/1114405050/q1/test_solution.py new file mode 100644 index 000000000..03ef3e382 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q1/test_solution.py @@ -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() diff --git a/weeks/week-18/solutions/1114405050/q2/AI_LOG.md b/weeks/week-18/solutions/1114405050/q2/AI_LOG.md new file mode 100644 index 000000000..3c171c65f --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q2/AI_LOG.md @@ -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` diff --git a/weeks/week-18/solutions/1114405050/q2/solution.py b/weeks/week-18/solutions/1114405050/q2/solution.py new file mode 100644 index 000000000..f45c6640d --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q2/solution.py @@ -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) diff --git a/weeks/week-18/solutions/1114405050/q2/test_solution.py b/weeks/week-18/solutions/1114405050/q2/test_solution.py new file mode 100644 index 000000000..7ec982fb2 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q2/test_solution.py @@ -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() diff --git a/weeks/week-18/solutions/1114405050/q3/AI_LOG.md b/weeks/week-18/solutions/1114405050/q3/AI_LOG.md new file mode 100644 index 000000000..9793b1e1e --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q3/AI_LOG.md @@ -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` diff --git a/weeks/week-18/solutions/1114405050/q3/solution.py b/weeks/week-18/solutions/1114405050/q3/solution.py new file mode 100644 index 000000000..5bb84ed6a --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q3/solution.py @@ -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 diff --git a/weeks/week-18/solutions/1114405050/q3/test_solution.py b/weeks/week-18/solutions/1114405050/q3/test_solution.py new file mode 100644 index 000000000..aea8f16a1 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q3/test_solution.py @@ -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() diff --git a/weeks/week-18/solutions/1114405050/q4/AI_LOG.md b/weeks/week-18/solutions/1114405050/q4/AI_LOG.md new file mode 100644 index 000000000..cfcc81aa2 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q4/AI_LOG.md @@ -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. 結論 +- 二分搜尋在搜尋效率與比較次數上有絕對優勢,但其前提是資料必須已經排序。 +- 線性搜尋優勢在於不需排序且程式邏輯極其簡單,在資料量極小或未排序時較靈活。 diff --git a/weeks/week-18/solutions/1114405050/q4/plot.py b/weeks/week-18/solutions/1114405050/q4/plot.py new file mode 100644 index 000000000..4dea50cc5 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q4/plot.py @@ -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() diff --git a/weeks/week-18/solutions/1114405050/q4/radar.png b/weeks/week-18/solutions/1114405050/q4/radar.png new file mode 100644 index 000000000..f0e9a62a6 Binary files /dev/null and b/weeks/week-18/solutions/1114405050/q4/radar.png differ diff --git a/weeks/week-18/solutions/1114405050/q4/solution.py b/weeks/week-18/solutions/1114405050/q4/solution.py new file mode 100644 index 000000000..2c6249f13 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q4/solution.py @@ -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 diff --git a/weeks/week-18/solutions/1114405050/q4/test_solution.py b/weeks/week-18/solutions/1114405050/q4/test_solution.py new file mode 100644 index 000000000..75c12ea53 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/q4/test_solution.py @@ -0,0 +1,30 @@ +import unittest +from solution import linear_search, binary_search + +class TestSearchAlgorithms(unittest.TestCase): + def setUp(self): + self.sorted_arr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150] + self.target = 138 # 假設目標為 100 + 50 (學號末兩碼) = 150 -> 這裡先測 130 看看 + + def test_binary_search_found(self): + # 測試二分搜尋找到目標 + found, idx, count = binary_search(self.sorted_arr, 130) + self.assertTrue(found) + self.assertEqual(idx, 12) + self.assertLessEqual(count, 4) # log2(15) ~ 3.9 + + def test_binary_search_not_found(self): + # 測試二分搜尋沒找到目標 + found, idx, count = binary_search(self.sorted_arr, 138) + self.assertFalse(found) + self.assertEqual(idx, -1) + + def test_linear_search_found(self): + # 測試線性搜尋找到目標 + found, idx, count = linear_search(self.sorted_arr, 130) + self.assertTrue(found) + self.assertEqual(idx, 12) + self.assertEqual(count, 13) # 第 13 個元素 + +if __name__ == '__main__': + unittest.main() diff --git a/weeks/week-18/solutions/1114405050/solution.py b/weeks/week-18/solutions/1114405050/solution.py new file mode 100644 index 000000000..037c193f0 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/solution.py @@ -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 diff --git a/weeks/week-18/solutions/1114405050/test_solution.py b/weeks/week-18/solutions/1114405050/test_solution.py new file mode 100644 index 000000000..03ef3e382 --- /dev/null +++ b/weeks/week-18/solutions/1114405050/test_solution.py @@ -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()