Let’s go back to basics for a minute.
What’s the definition of the Euclidean algorithm?
First off, the algorithm only contains a single precondition:
Numbers must be positive.
Nothing about the order of a
and b
is mentioned, making the first six lines of your function redundant:
def gcd(r1, r2):
if r1 % r2 == 0:
print(r2)
else:
gcd(r2, r1 % r2)
(I’ve also formatted the code in accordance with PEP8, mentioned previously.)
Next, the stop condition of the algorithm isn’t that r1 % r2 == 0
. It is:
a remainder rN must eventually equal zero, at which point the algorithm stops
So we only need to test if r2 == 0
, saving us one computation:
def gcd(r1, r2):
if r2 == 0:
print(r1)
else:
gcd(r2, r1 % r2)
This is as far as we are going to take the algorithm itself for now. Now for some more general remarks:
Functions should either compute a result, or handle I/O. Never both. So return the GCD instead:
def gcd(r1, r2):
if r2 == 0:
return r1
else:
return gcd(r2, r1 % r2)
This function consists of a single test with two return statements. It’s idiomatic to rewrite such statements into a single return statement with a conditional value:
def gcd(r1, r2):
return r1 if r2 == 0 else gcd(r2, r1 % r2)
These last two forms are large equivalent. Some people (me included) find the second version more readable but in Python I can see how one might disagree, due to Python’s syntax: other languages write if a then b else c
, or a ? b : c
; Python, by contrast, forces you to write b if a else c
; what?!
Now, although the recursive form of this algorithm is idiomatic and elegant, it’s true this isn’t pythonic: Python (mostly due to its main developer’s whim rather than technical reasons) eschews recursion in favour of iteration.
Luckily the iterative form in this case is likewise elegant and relatively simple: it hinges on the recognition that the recursive algorithm progressively swaps the values of the remainders with the next remainder in the series (although now the variable names are even more misleading: we should really be using rN and rN+1:
def gcd(r1, r2):
while r2 != 0:
r1, r2 = r2, r1 % r2
return r1
Bonus: Note that all the above implementations implicitly rely on the precondition: they yield wrong results with negative input. Luckily, we can extend the implementation trivially to handle negative inputs, and the good news is: we don’t need to modify the inputs in any way. We simply need to make the output positive:
def gcd(r1, r2):
while r2 != 0:
r1, r2 = r2, r1 % r2
return abs(r1)
And of course the same also works on the recursive form:
def gcd(r1, r2):
return abs(r1) if r2 == 0 else gcd(r2, r1 % r2)
from math import gcd
\$\endgroup\$