Skip to content

Commit f309bcf

Browse files
committed
refactor: modularize Collatz logic and add comprehensive test suite
1 parent 6f298a1 commit f309bcf

2 files changed

Lines changed: 171 additions & 67 deletions

File tree

Lines changed: 75 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
1-
print("🎮 The Collatz Conjecture Sequence 🎮")
2-
print("Also known as the 3n+1 problem\n")
3-
print("📚 Rules:")
4-
print(" - If the number is even: divide by 2")
5-
print(" - If the number is odd: multiply by 3 and add 1")
6-
print(" - Continue until you reach 1\n")
71

2+
import sys
3+
from typing import List, Generator
84
MAX_STEPS = 100000
95
MAX_VALUE = 10**18
106
MAX_INPUT = 10**12
117
steps_cache = {1: 0}
128

13-
14-
from typing import List, Generator
15-
169
def collatz_next(n: int) -> int:
1710
"""Calculate the next number in the Collatz sequence."""
1811
return n // 2 if n % 2 == 0 else 3 * n + 1
@@ -66,63 +59,78 @@ def collatz_sequence(start: int) -> Generator[int, None, None]:
6659
steps_cache[value] = total
6760

6861

69-
while True:
70-
try:
71-
number = int(input("🎯 Enter a positive integer to start: "))
72-
if number <= 0:
73-
print("❌ Please enter a positive integer!")
62+
def main():
63+
64+
print("🎮 The Collatz Conjecture Sequence 🎮")
65+
print("Also known as the 3n+1 problem\n")
66+
print("📚 Rules:")
67+
print(" - If the number is even: divide by 2")
68+
print(" - If the number is odd: multiply by 3 and add 1")
69+
print(" - Continue until you reach 1\n")
70+
71+
72+
while True:
73+
try:
74+
user_input = input("🎯 Enter a positive integer to start: ").strip()
75+
if not user_input:
76+
break
77+
number=int(user_input)
78+
if number <= 0:
79+
print("❌ Please enter a positive integer!")
80+
continue
81+
if number > MAX_INPUT:
82+
print(f"⚠️ Input too large! Maximum allowed: {MAX_INPUT:,}")
83+
continue
84+
except ValueError:
85+
print("❌ Please enter a valid number!")
7486
continue
75-
if number > MAX_INPUT:
76-
print(f"⚠️ Input too large! Maximum allowed: {MAX_INPUT:,}")
77-
continue
78-
except ValueError:
79-
print("❌ Please enter a valid number!")
80-
continue
81-
82-
original_number = number
83-
84-
print(f"\n🚀 Starting with: {number}")
85-
print("📊 Sequence:")
8687

87-
sequence = []
88-
steps = 0
89-
max_value = number
90-
91-
gen = collatz_sequence(number)
92-
93-
first = next(gen)
94-
print(first, end="")
95-
sequence.append(first)
96-
97-
for num in gen:
98-
sequence.append(num)
99-
steps += 1
100-
max_value = max(max_value, num)
101-
print(f" ➡️ {num}", end="")
102-
if steps % 10 == 0:
103-
print()
104-
105-
print("\n\n✅ SEQUENCE COMPLETE!")
106-
print(f"📍 Starting number: {original_number}")
107-
print(f"👣 Total steps: {steps}")
108-
print(f"📏 Sequence length: {len(sequence)}")
109-
print(f"🏆 Highest number reached: {max_value}")
110-
111-
if len(sequence) <= 100:
112-
view_details = input("\n🔍 Would you like to see step-by-step details? (y/n): ").strip().lower()
113-
if view_details in ['y', 'yes']:
114-
print("\n📝 Detailed Steps:")
115-
for i in range(len(sequence) - 1):
116-
current = sequence[i]
117-
next_num = sequence[i + 1]
118-
if current % 2 == 0:
119-
print(f" Step {i + 1}: {current} is even ➡️ {current} ÷ 2 = {next_num}")
120-
else:
121-
print(f" Step {i + 1}: {current} is odd ➡️ ({current} × 3) + 1 = {next_num}")
122-
123-
print("\n🎉 The sequence reached 1 as expected!")
124-
125-
again = input("\n🔄 Do you want to test another number? (y/n): ").strip().lower()
126-
if again != 'y':
127-
print("\n👋 Thanks for exploring the Collatz Conjecture! Goodbye!\n")
128-
break
88+
original_number = number
89+
90+
print(f"\n🚀 Starting with: {number}")
91+
print("📊 Sequence:")
92+
93+
sequence = []
94+
steps = 0
95+
max_value = number
96+
97+
gen = collatz_sequence(number)
98+
99+
first = next(gen)
100+
print(first, end="")
101+
sequence.append(first)
102+
103+
for num in gen:
104+
sequence.append(num)
105+
steps += 1
106+
max_value = max(max_value, num)
107+
print(f" ➡️ {num}", end="")
108+
if steps % 10 == 0:
109+
print()
110+
111+
print("\n\n✅ SEQUENCE COMPLETE!")
112+
print(f"📍 Starting number: {original_number}")
113+
print(f"👣 Total steps: {steps}")
114+
print(f"📏 Sequence length: {len(sequence)}")
115+
print(f"🏆 Highest number reached: {max_value}")
116+
117+
if len(sequence) <= 100:
118+
view_details = input("\n🔍 Would you like to see step-by-step details? (y/n): ").strip().lower()
119+
if view_details in ['y', 'yes']:
120+
print("\n📝 Detailed Steps:")
121+
for i in range(len(sequence) - 1):
122+
current = sequence[i]
123+
next_num = sequence[i + 1]
124+
if current % 2 == 0:
125+
print(f" Step {i + 1}: {current} is even ➡️ {current} ÷ 2 = {next_num}")
126+
else:
127+
print(f" Step {i + 1}: {current} is odd ➡️ ({current} × 3) + 1 = {next_num}")
128+
129+
print("\n🎉 The sequence reached 1 as expected!")
130+
131+
again = input("\n🔄 Do you want to test another number? (y/n): ").strip().lower()
132+
if again != 'y':
133+
print("\n👋 Thanks for exploring the Collatz Conjecture! Goodbye!\n")
134+
break
135+
if __name__ == "__main__":
136+
main()

tests/test_collatz_conjecture.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import unittest
2+
import subprocess
3+
import os
4+
5+
class TestCollatzConjecture(unittest.TestCase):
6+
def setUp(self):
7+
# Resolve the absolute path to the script
8+
self.script_path = os.path.join(
9+
os.path.dirname(__file__), "..",
10+
"math", "Collatz-Conjecture", "Collatz-Conjecture.py"
11+
)
12+
self.script_path = os.path.abspath(self.script_path)
13+
14+
def run_script(self, inputs):
15+
"""Feeding simulated user inputs to the script."""
16+
input_data = "\n".join(inputs + ["n"]) + "\n"
17+
env = os.environ.copy()
18+
env["PYTHONIOENCODING"] = "utf-8"
19+
20+
result = subprocess.run(
21+
["python", self.script_path],
22+
input=input_data,
23+
text=True,
24+
capture_output=True,
25+
encoding='utf-8',
26+
env=env
27+
)
28+
return result.stdout
29+
30+
def test_standard_flow(self):
31+
"""Test a simple number sequence (4 -> 2 -> 1)."""
32+
output = self.run_script(["4", "n"])
33+
self.assertIn("Starting with: 4", output)
34+
self.assertIn("Total steps: 2", output)
35+
self.assertIn("✅ SEQUENCE COMPLETE!", output)
36+
37+
def test_detailed_explanation(self):
38+
"""Test the 'y' prompt for step-by-step details."""
39+
output = self.run_script(["3", "y"])
40+
self.assertIn("Detailed Steps:", output)
41+
self.assertIn("3 is odd ➡️ (3 x 3) + 1 = 10", output)
42+
self.assertIn("10 is even ➡️ 10 ÷ 2 = 5", output)
43+
44+
def test_mathematical_power_of_two(self):
45+
"""Test a power of two (16), which should go straight to 1."""
46+
# 16 -> 8 -> 4 -> 2 -> 1 (4 steps)
47+
output = self.run_script(["16", "n", "n"])
48+
self.assertIn("Total steps: 4", output)
49+
self.assertIn("Highest number reached: 16", output)
50+
51+
52+
def test_input_case_insensitivity(self):
53+
"""Ensure 'Y' and 'YES' work just as well as 'y'."""
54+
output = self.run_script(["3", "YES", "Y", "2", "N", "N"])
55+
self.assertIn("Detailed Steps:", output) # Proves YES worked
56+
self.assertIn("Starting with: 2", output) # Proves Y worked for 'again'
57+
58+
def test_recovery_from_multiple_bad_inputs(self):
59+
"""Test if the script can handle 3 errors in a row and still work."""
60+
output = self.run_script(["abc", "-10", "0", "5", "n", "n"])
61+
self.assertIn("❌ Please enter a valid number!", output)
62+
self.assertIn("❌ Please enter a positive integer!", output)
63+
self.assertIn("Starting with: 5", output) # Proves it eventually succeeded
64+
65+
def test_complex_sequence_27(self):
66+
"""Test number 27, known for its long path (111 steps)."""
67+
output = self.run_script(["27", "n", "n"])
68+
self.assertIn("Total steps: 111", output)
69+
self.assertIn("Highest number reached: 9232", output)
70+
71+
def test_cache_logic_via_loop(self):
72+
"""Run the script twice to verify the loop and goodbye message work."""
73+
# 4 (no details, yes again), then 2 (no details, no again)
74+
output = self.run_script(["4", "n", "y", "2", "n"])
75+
self.assertIn("Starting with: 4", output)
76+
self.assertIn("Starting with: 2", output)
77+
self.assertIn("Goodbye!", output)
78+
79+
def test_invalid_string_input(self):
80+
"""Ensure script handles non-integers without crashing."""
81+
output = self.run_script(["abc", "1", "n"])
82+
self.assertIn("❌ Please enter a valid number!", output)
83+
84+
def test_zero_input_validation(self):
85+
"""Ensure script rejects zero or negative integers."""
86+
output = self.run_script(["0", "1", "n"])
87+
self.assertIn("❌ Please enter a positive integer!", output)
88+
89+
def test_large_input_guardrail(self):
90+
"""Test the MAX_INPUT (10^12) constraint."""
91+
large_val = str(10**12 + 1)
92+
output = self.run_script([large_val, "1", "n"])
93+
self.assertIn("⚠️ Input too large!", output)
94+
95+
if __name__ == '__main__':
96+
unittest.main()

0 commit comments

Comments
 (0)