|
1 | 1 | """ |
2 | | -Gcd of N Numbers |
3 | | -Reference: https://en.wikipedia.org/wiki/Greatest_common_divisor |
| 2 | +GCD of N Numbers |
| 3 | +
|
| 4 | +This module calculates the Greatest Common Divisor (GCD) |
| 5 | +of multiple positive integers using prime factorization. |
| 6 | +
|
| 7 | +Reference: |
| 8 | +https://en.wikipedia.org/wiki/Greatest_common_divisor |
4 | 9 | """ |
5 | 10 |
|
6 | 11 | from collections import Counter |
| 12 | +from math import isqrt |
7 | 13 |
|
8 | 14 |
|
9 | 15 | def get_factors( |
10 | | - number: int, factors: Counter | None = None, factor: int = 2 |
| 16 | + number: int, prime_factors: Counter | None = None, divisor: int = 2 |
11 | 17 | ) -> Counter: |
12 | 18 | """ |
13 | | - this is a recursive function for get all factors of number |
| 19 | + Recursively computes the PRIME FACTORIZATION of a positive integer. |
| 20 | +
|
| 21 | + The result is returned as a Counter where: |
| 22 | + - key → prime factor |
| 23 | + - value → power of that prime factor |
| 24 | +
|
| 25 | + Examples: |
14 | 26 | >>> get_factors(45) |
15 | 27 | Counter({3: 2, 5: 1}) |
16 | 28 | >>> get_factors(2520) |
17 | 29 | Counter({2: 3, 3: 2, 5: 1, 7: 1}) |
18 | 30 | >>> get_factors(23) |
19 | 31 | Counter({23: 1}) |
20 | | - >>> get_factors(0) |
21 | | - Traceback (most recent call last): |
22 | | - ... |
23 | | - TypeError: number must be integer and greater than zero |
24 | | - >>> get_factors(-1) |
25 | | - Traceback (most recent call last): |
26 | | - ... |
27 | | - TypeError: number must be integer and greater than zero |
28 | | - >>> get_factors(1.5) |
29 | | - Traceback (most recent call last): |
30 | | - ... |
31 | | - TypeError: number must be integer and greater than zero |
32 | | -
|
33 | | - factor can be all numbers from 2 to number that we check if number % factor == 0 |
34 | | - if it is equal to zero, we check again with number // factor |
35 | | - else we increase factor by one |
| 32 | + >>> get_factors(1) |
| 33 | + Counter() |
36 | 34 | """ |
37 | 35 |
|
38 | | - match number: |
39 | | - case int(number) if number == 1: |
40 | | - return Counter({1: 1}) |
41 | | - case int(num) if number > 0: |
42 | | - number = num |
43 | | - case _: |
44 | | - raise TypeError("number must be integer and greater than zero") |
45 | | - |
46 | | - factors = factors or Counter() |
47 | | - |
48 | | - if number == factor: # break condition |
49 | | - # all numbers are factors of itself |
50 | | - factors[factor] += 1 |
51 | | - return factors |
52 | | - |
53 | | - if number % factor > 0: |
54 | | - # if it is greater than zero |
55 | | - # so it is not a factor of number and we check next number |
56 | | - return get_factors(number, factors, factor + 1) |
57 | | - |
58 | | - factors[factor] += 1 |
59 | | - # else we update factors (that is Counter(dict-like) type) and check again |
60 | | - return get_factors(number // factor, factors, factor) |
| 36 | + # ----------------------------- |
| 37 | + # Input Validation |
| 38 | + # ----------------------------- |
| 39 | + # The function should only accept positive integers. |
| 40 | + # Any other input is invalid. |
| 41 | + if not isinstance(number, int) or number <= 0: |
| 42 | + raise TypeError("number must be an integer greater than zero") |
| 43 | + |
| 44 | + # ----------------------------- |
| 45 | + # Base Case: number == 1 |
| 46 | + # ----------------------------- |
| 47 | + # 1 has NO prime factors. |
| 48 | + # Returning an empty Counter is mathematically correct. |
| 49 | + if number == 1: |
| 50 | + return Counter() |
| 51 | + |
| 52 | + # ----------------------------- |
| 53 | + # Initialize Counter |
| 54 | + # ----------------------------- |
| 55 | + # If this is the first call, create a new Counter. |
| 56 | + # Otherwise, reuse the existing one (recursive calls). |
| 57 | + prime_factors = prime_factors or Counter() |
| 58 | + |
| 59 | + # ----------------------------- |
| 60 | + # Optimization (IMPORTANT) |
| 61 | + # ----------------------------- |
| 62 | + # If divisor is greater than sqrt(number), |
| 63 | + # then the remaining number itself must be prime. |
| 64 | + # |
| 65 | + # Example: |
| 66 | + # If number = 23, no divisor <= sqrt(23) divides it, |
| 67 | + # so 23 is prime. |
| 68 | + if divisor > isqrt(number): |
| 69 | + prime_factors[number] += 1 |
| 70 | + return prime_factors |
| 71 | + |
| 72 | + # ----------------------------- |
| 73 | + # If divisor divides the number |
| 74 | + # ----------------------------- |
| 75 | + if number % divisor == 0: |
| 76 | + # divisor is a prime factor |
| 77 | + prime_factors[divisor] += 1 |
| 78 | + |
| 79 | + # Continue factoring the reduced number |
| 80 | + return get_factors(number // divisor, prime_factors, divisor) |
| 81 | + |
| 82 | + # ----------------------------- |
| 83 | + # If divisor does NOT divide the number |
| 84 | + # ----------------------------- |
| 85 | + # Try the next possible divisor |
| 86 | + return get_factors(number, prime_factors, divisor + 1) |
61 | 87 |
|
62 | 88 |
|
63 | 89 | def get_greatest_common_divisor(*numbers: int) -> int: |
64 | 90 | """ |
65 | | - get gcd of n numbers: |
| 91 | + Computes the Greatest Common Divisor (GCD) |
| 92 | + of one or more positive integers. |
| 93 | +
|
| 94 | + The approach: |
| 95 | + 1. Compute prime factors of each number |
| 96 | + 2. Find common factors using Counter intersection |
| 97 | + 3. Multiply common factors to get GCD |
| 98 | +
|
| 99 | + Examples: |
66 | 100 | >>> get_greatest_common_divisor(18, 45) |
67 | 101 | 9 |
68 | 102 | >>> get_greatest_common_divisor(23, 37) |
69 | 103 | 1 |
70 | 104 | >>> get_greatest_common_divisor(2520, 8350) |
71 | 105 | 10 |
72 | | - >>> get_greatest_common_divisor(-10, 20) |
73 | | - Traceback (most recent call last): |
74 | | - ... |
75 | | - Exception: numbers must be integer and greater than zero |
76 | | - >>> get_greatest_common_divisor(1.5, 2) |
77 | | - Traceback (most recent call last): |
78 | | - ... |
79 | | - Exception: numbers must be integer and greater than zero |
80 | | - >>> get_greatest_common_divisor(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) |
81 | | - 1 |
82 | | - >>> get_greatest_common_divisor("1", 2, 3, 4, 5, 6, 7, 8, 9, 10) |
83 | | - Traceback (most recent call last): |
84 | | - ... |
85 | | - Exception: numbers must be integer and greater than zero |
86 | 106 | """ |
87 | 107 |
|
88 | | - # we just need factors, not numbers itself |
| 108 | + # ----------------------------- |
| 109 | + # Factorize all numbers |
| 110 | + # ----------------------------- |
| 111 | + # map(get_factors, numbers) converts: |
| 112 | + # (18, 45) → [Counter({2:1,3:2}), Counter({3:2,5:1})] |
89 | 113 | try: |
90 | | - same_factors, *factors = map(get_factors, numbers) |
91 | | - except TypeError as e: |
92 | | - raise Exception("numbers must be integer and greater than zero") from e |
93 | | - |
94 | | - for factor in factors: |
95 | | - same_factors &= factor |
96 | | - # get common factor between all |
97 | | - # `&` return common elements with smaller value (for Counter type) |
98 | | - |
99 | | - # now, same_factors is something like {2: 2, 3: 4} that means 2 * 2 * 3 * 3 * 3 * 3 |
100 | | - mult = 1 |
101 | | - # power each factor and multiply |
102 | | - # for {2: 2, 3: 4}, it is [4, 81] and then 324 |
103 | | - for m in [factor**power for factor, power in same_factors.items()]: |
104 | | - mult *= m |
105 | | - return mult |
106 | | - |
107 | | - |
| 114 | + common_factors, *other_factors = map(get_factors, numbers) |
| 115 | + except TypeError as exc: |
| 116 | + # Raised if any input is invalid |
| 117 | + raise ValueError("numbers must be integers greater than zero") from exc |
| 118 | + |
| 119 | + # ----------------------------- |
| 120 | + # Find common prime factors |
| 121 | + # ----------------------------- |
| 122 | + # Counter '&' operator keeps: |
| 123 | + # - only common keys |
| 124 | + # - minimum power for each key |
| 125 | + # |
| 126 | + # Example: |
| 127 | + # Counter({2:1, 3:2}) & Counter({3:2, 5:1}) |
| 128 | + # → Counter({3:2}) |
| 129 | + for factors in other_factors: |
| 130 | + common_factors &= factors |
| 131 | + |
| 132 | + # ----------------------------- |
| 133 | + # Multiply common factors |
| 134 | + # ----------------------------- |
| 135 | + # Convert factorization back into a number. |
| 136 | + # Example: |
| 137 | + # Counter({2:2, 3:1}) → 2² * 3¹ = 12 |
| 138 | + gcd_value = 1 |
| 139 | + for factor, power in common_factors.items(): |
| 140 | + gcd_value *= factor ** power |
| 141 | + |
| 142 | + return gcd_value |
| 143 | + |
| 144 | + |
| 145 | +# ----------------------------- |
| 146 | +# Manual Test |
| 147 | +# ----------------------------- |
108 | 148 | if __name__ == "__main__": |
109 | | - print(get_greatest_common_divisor(18, 45)) # 9 |
| 149 | + print(get_greatest_common_divisor(18, 45)) # Expected output: 9 |
0 commit comments