diff --git a/src/fraction_simplifier.py b/src/fraction_simplifier.py new file mode 100644 index 00000000..c61658cb --- /dev/null +++ b/src/fraction_simplifier.py @@ -0,0 +1,46 @@ +from math import gcd + +def simplify_fraction(numerator: int, denominator: int) -> tuple: + """ + Simplify a fraction to its lowest terms. + + Args: + numerator (int): The numerator of the fraction + denominator (int): The denominator of the fraction + + Returns: + tuple: A tuple containing the simplified numerator and denominator + + Raises: + ValueError: If denominator is zero + TypeError: If inputs are not integers + """ + # Validate inputs + if not isinstance(numerator, int) or not isinstance(denominator, int): + raise TypeError("Numerator and denominator must be integers") + + # Check for zero denominator + if denominator == 0: + raise ValueError("Denominator cannot be zero") + + # Handle special case of zero numerator + if numerator == 0: + return (0, 1) + + # Determine the sign + sign = 1 + if numerator * denominator < 0: + sign = -1 + + # Work with absolute values + numerator = abs(numerator) + denominator = abs(denominator) + + # Find the greatest common divisor + divisor = gcd(numerator, denominator) + + # Simplify the fraction + simplified_numerator = sign * (numerator // divisor) + simplified_denominator = denominator // divisor + + return (simplified_numerator, simplified_denominator) \ No newline at end of file diff --git a/src/gcd_calculator.py b/src/gcd_calculator.py new file mode 100644 index 00000000..b68ae915 --- /dev/null +++ b/src/gcd_calculator.py @@ -0,0 +1,60 @@ +from src.prime_factorization import prime_factorization + +def gcd_using_prime_factors(a, b): + """ + Calculate the Greatest Common Divisor (GCD) of two numbers using prime factorization. + + This function computes the GCD by finding the common prime factors between + the two input numbers and multiplying them. + + Args: + a (int): First positive integer. + b (int): Second positive integer. + + Returns: + int: The Greatest Common Divisor of a and b. + + Raises: + ValueError: If either input is not a positive integer. + """ + # Validate inputs + if not isinstance(a, int) or not isinstance(b, int): + raise ValueError("Inputs must be integers") + + if a <= 0 or b <= 0: + raise ValueError("Inputs must be positive integers") + + # Special case: if either number is 1, GCD is 1 + if a == 1 or b == 1: + return 1 + + # Get prime factorizations of both numbers + a_factors = prime_factorization(a) + b_factors = prime_factorization(b) + + # Find common prime factors + gcd_factors = [] + a_factor_count = {} + b_factor_count = {} + + # Count occurrences of prime factors + for factor in a_factors: + a_factor_count[factor] = a_factor_count.get(factor, 0) + 1 + + for factor in b_factors: + b_factor_count[factor] = b_factor_count.get(factor, 0) + 1 + + # Determine common prime factors with their minimum occurrence + for factor in set(a_factor_count.keys()) & set(b_factor_count.keys()): + common_count = min(a_factor_count[factor], b_factor_count[factor]) + gcd_factors.extend([factor] * common_count) + + # Calculate GCD by multiplying common prime factors + if not gcd_factors: + return 1 # If no common factors, GCD is 1 + + gcd = 1 + for factor in gcd_factors: + gcd *= factor + + return gcd \ No newline at end of file diff --git a/src/lcm_calculator.py b/src/lcm_calculator.py new file mode 100644 index 00000000..986648b9 --- /dev/null +++ b/src/lcm_calculator.py @@ -0,0 +1,56 @@ +from src.gcd_calculator import gcd_using_prime_factors as gcd + +def lcm(a: int, b: int) -> int: + """ + Calculate the Least Common Multiple (LCM) of two integers. + + The LCM is calculated using the formula: LCM(a,b) = |a * b| / GCD(a,b) + + Args: + a (int): First integer + b (int): Second integer + + Returns: + int: The Least Common Multiple of a and b + + Raises: + ValueError: If either input is not a positive integer + TypeError: If inputs are not integers + """ + # Type checking + if not isinstance(a, int) or not isinstance(b, int): + raise TypeError("Inputs must be integers") + + # Check for positive integers + if a <= 0 or b <= 0: + raise ValueError("Inputs must be positive integers") + + # Calculate LCM using the formula: LCM(a,b) = |a * b| / GCD(a,b) + return abs(a * b) // gcd(a, b) + +def lcm_multiple(*args: int) -> int: + """ + Calculate the Least Common Multiple of multiple integers. + + Args: + *args (int): Variable number of positive integers + + Returns: + int: The Least Common Multiple of all input integers + + Raises: + ValueError: If no arguments are provided or any argument is not a positive integer + TypeError: If any input is not an integer + """ + # Check if arguments are provided + if len(args) == 0: + raise ValueError("At least one integer is required") + + # Initialize result with the first number + result = args[0] + + # Calculate LCM for all subsequent numbers + for num in args[1:]: + result = lcm(result, num) + + return result \ No newline at end of file diff --git a/src/prime_checker.py b/src/prime_checker.py new file mode 100644 index 00000000..0b9ce838 --- /dev/null +++ b/src/prime_checker.py @@ -0,0 +1,28 @@ +def is_prime(number): + """ + Check if a given number is prime. + + Args: + number (int): The number to check for primality. + + Returns: + bool: True if the number is prime, False otherwise. + + Raises: + ValueError: If the input is not a positive integer. + """ + # Validate input + if not isinstance(number, int): + raise ValueError("Input must be an integer") + + # Handle edge cases + if number < 2: + return False + + # Check for primality using trial division + # Only need to check up to the square root of the number + for i in range(2, int(number**0.5) + 1): + if number % i == 0: + return False + + return True \ No newline at end of file diff --git a/src/prime_factorization.py b/src/prime_factorization.py new file mode 100644 index 00000000..040f69e9 --- /dev/null +++ b/src/prime_factorization.py @@ -0,0 +1,47 @@ +def prime_factorization(n): + """ + Compute the prime factorization of a given positive integer. + + Args: + n (int): A positive integer to factorize. + + Returns: + list: A list of prime factors of the input number. + + Raises: + ValueError: If the input is not a positive integer. + """ + # Validate input + if not isinstance(n, int): + raise ValueError("Input must be an integer") + + if n <= 0: + raise ValueError("Input must be a positive integer") + + # Special case for 1 + if n == 1: + return [] + + # List to store prime factors + factors = [] + + # Handle 2 as a special case first + while n % 2 == 0: + factors.append(2) + n //= 2 + + # Check for odd prime factors + factor = 3 + while factor * factor <= n: + # If factor divides n, add it to factors + while n % factor == 0: + factors.append(factor) + n //= factor + # Move to next potential prime factor + factor += 2 + + # If n is a prime number greater than 2 + if n > 2: + factors.append(n) + + return factors \ No newline at end of file diff --git a/tests/test_fraction_simplifier.py b/tests/test_fraction_simplifier.py new file mode 100644 index 00000000..8bc3dbcf --- /dev/null +++ b/tests/test_fraction_simplifier.py @@ -0,0 +1,39 @@ +import pytest +from src.fraction_simplifier import simplify_fraction + +def test_simplify_positive_fraction(): + """Test simplifying a positive fraction""" + assert simplify_fraction(4, 6) == (2, 3) + +def test_simplify_negative_fraction(): + """Test simplifying a negative fraction""" + assert simplify_fraction(-4, 6) == (-2, 3) + assert simplify_fraction(4, -6) == (-2, 3) + assert simplify_fraction(-4, -6) == (2, 3) + +def test_zero_numerator(): + """Test fraction with zero numerator""" + assert simplify_fraction(0, 5) == (0, 1) + assert simplify_fraction(0, -5) == (0, 1) + +def test_already_simplified_fraction(): + """Test a fraction that is already in its simplest form""" + assert simplify_fraction(5, 7) == (5, 7) + +def test_large_fraction(): + """Test simplifying a larger fraction""" + assert simplify_fraction(48, 180) == (4, 15) + +def test_zero_denominator(): + """Test that zero denominator raises a ValueError""" + with pytest.raises(ValueError, match="Denominator cannot be zero"): + simplify_fraction(1, 0) + +def test_invalid_input_types(): + """Test that non-integer inputs raise a TypeError""" + with pytest.raises(TypeError, match="Numerator and denominator must be integers"): + simplify_fraction(1.5, 2) + with pytest.raises(TypeError, match="Numerator and denominator must be integers"): + simplify_fraction(1, "2") + with pytest.raises(TypeError, match="Numerator and denominator must be integers"): + simplify_fraction("1", 2) \ No newline at end of file diff --git a/tests/test_gcd_calculator.py b/tests/test_gcd_calculator.py new file mode 100644 index 00000000..05280493 --- /dev/null +++ b/tests/test_gcd_calculator.py @@ -0,0 +1,44 @@ +import pytest +from src.gcd_calculator import gcd_using_prime_factors + +def test_gcd_basic_cases(): + """Test basic GCD calculations.""" + assert gcd_using_prime_factors(48, 18) == 6 + assert gcd_using_prime_factors(54, 24) == 6 + assert gcd_using_prime_factors(100, 75) == 25 + +def test_gcd_coprime_numbers(): + """Test GCD of coprime numbers.""" + assert gcd_using_prime_factors(7, 13) == 1 + assert gcd_using_prime_factors(11, 17) == 1 + +def test_gcd_same_number(): + """Test GCD when both numbers are the same.""" + assert gcd_using_prime_factors(12, 12) == 12 + assert gcd_using_prime_factors(17, 17) == 17 + +def test_gcd_one_is_multiple(): + """Test GCD when one number is a multiple of the other.""" + assert gcd_using_prime_factors(24, 8) == 8 + assert gcd_using_prime_factors(8, 24) == 8 + +def test_gcd_one_is_one(): + """Test GCD when one number is 1.""" + assert gcd_using_prime_factors(1, 15) == 1 + assert gcd_using_prime_factors(15, 1) == 1 + +def test_gcd_invalid_inputs(): + """Test error handling for invalid inputs.""" + with pytest.raises(ValueError, match="Inputs must be integers"): + gcd_using_prime_factors(10.5, 15) + + with pytest.raises(ValueError, match="Inputs must be positive integers"): + gcd_using_prime_factors(0, 15) + + with pytest.raises(ValueError, match="Inputs must be positive integers"): + gcd_using_prime_factors(10, -5) + +def test_gcd_large_numbers(): + """Test GCD calculation for larger numbers.""" + assert gcd_using_prime_factors(1260, 1680) == 420 + assert gcd_using_prime_factors(123456, 789012) == 12 \ No newline at end of file diff --git a/tests/test_lcm_calculator.py b/tests/test_lcm_calculator.py new file mode 100644 index 00000000..c84c9bec --- /dev/null +++ b/tests/test_lcm_calculator.py @@ -0,0 +1,51 @@ +import pytest +from src.lcm_calculator import lcm, lcm_multiple + +def test_lcm_basic_functionality(): + """Test basic LCM calculations""" + assert lcm(4, 6) == 12 + assert lcm(21, 6) == 42 + assert lcm(17, 5) == 85 + +def test_lcm_same_number(): + """Test LCM when both numbers are the same""" + assert lcm(7, 7) == 7 + assert lcm(13, 13) == 13 + +def test_lcm_one_is_multiple(): + """Test LCM when one number is a multiple of the other""" + assert lcm(8, 4) == 8 + assert lcm(15, 5) == 15 + +def test_lcm_coprime(): + """Test LCM of coprime numbers""" + assert lcm(7, 11) == 77 + assert lcm(13, 17) == 221 + +def test_lcm_multiple_integers(): + """Test LCM calculation with multiple integers""" + assert lcm_multiple(2, 3, 4) == 12 + assert lcm_multiple(3, 4, 6) == 12 + assert lcm_multiple(2, 3, 5, 7) == 210 + +def test_lcm_error_handling(): + """Test error handling for invalid inputs""" + # Test non-integer inputs + with pytest.raises(TypeError): + lcm(4.5, 6) + with pytest.raises(TypeError): + lcm("4", 6) + + # Test non-positive integer inputs + with pytest.raises(ValueError): + lcm(0, 6) + with pytest.raises(ValueError): + lcm(-4, 6) + + # Test multiple LCM with no arguments or invalid inputs + with pytest.raises(ValueError): + lcm_multiple() + with pytest.raises(TypeError): + lcm_multiple(2, 3, "4") + with pytest.raises(ValueError): + lcm_multiple(0, 3, 4) \ No newline at end of file diff --git a/tests/test_prime_checker.py b/tests/test_prime_checker.py new file mode 100644 index 00000000..f0317059 --- /dev/null +++ b/tests/test_prime_checker.py @@ -0,0 +1,37 @@ +import pytest +from src.prime_checker import is_prime + +def test_prime_numbers(): + """Test known prime numbers""" + prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + for num in prime_numbers: + assert is_prime(num) is True, f"{num} should be prime" + +def test_non_prime_numbers(): + """Test known non-prime numbers""" + non_prime_numbers = [0, 1, 4, 6, 8, 9, 10, 12, 14, 15] + for num in non_prime_numbers: + assert is_prime(num) is False, f"{num} should not be prime" + +def test_large_prime(): + """Test a large prime number""" + assert is_prime(104729) is True, "104729 is a prime number" + +def test_large_non_prime(): + """Test a large non-prime number""" + assert is_prime(104730) is False, "104730 is not a prime number" + +def test_negative_numbers(): + """Test that negative numbers are not prime""" + assert is_prime(-7) is False, "Negative numbers should not be prime" + +def test_invalid_input(): + """Test that invalid inputs raise a ValueError""" + with pytest.raises(ValueError): + is_prime(3.14) + + with pytest.raises(ValueError): + is_prime("not a number") + + with pytest.raises(ValueError): + is_prime(None) \ No newline at end of file diff --git a/tests/test_prime_factorization.py b/tests/test_prime_factorization.py new file mode 100644 index 00000000..4839fc87 --- /dev/null +++ b/tests/test_prime_factorization.py @@ -0,0 +1,39 @@ +import pytest +from src.prime_factorization import prime_factorization + +def test_prime_factorization_basic(): + """Test basic prime factorization""" + assert prime_factorization(12) == [2, 2, 3] + assert prime_factorization(15) == [3, 5] + assert prime_factorization(100) == [2, 2, 5, 5] + +def test_prime_factorization_prime_number(): + """Test prime factorization of prime numbers""" + assert prime_factorization(7) == [7] + assert prime_factorization(11) == [11] + assert prime_factorization(17) == [17] + +def test_prime_factorization_special_cases(): + """Test special cases""" + assert prime_factorization(1) == [] + assert prime_factorization(2) == [2] + +def test_prime_factorization_large_number(): + """Test prime factorization of a larger number""" + assert prime_factorization(84) == [2, 2, 3, 7] + +def test_prime_factorization_invalid_inputs(): + """Test error handling for invalid inputs""" + with pytest.raises(ValueError, match="Input must be an integer"): + prime_factorization("12") + + with pytest.raises(ValueError, match="Input must be a positive integer"): + prime_factorization(0) + + with pytest.raises(ValueError, match="Input must be a positive integer"): + prime_factorization(-5) + +def test_prime_factorization_large_prime(): + """Test prime factorization of a large prime number""" + large_prime = 104729 # A large prime number + assert prime_factorization(large_prime) == [104729] \ No newline at end of file