Recall that the problem that we're trying to solve is to count the number of trailing zeros in $n!$. Here is an example:
def fact(n):
res = 1
for i in range(1, n+1):
res *= i
return res
if __name__ == '__main__':
print(fact(20))
$20!$ has 4 trailing zeros -- zeros at the end of the number.
Here is the naive approach to the problem. First, compute $n!$, then, keep dividing the number by 10 (i.e., removing 0's), while that's possible; all the while, keep incrementing a counter that keeps track of how many times we divided by 10.
def trailing_zerosA(n):
'''Return the number of trailing zeros in n!
Arguments:
n - a positive integer
'''
n_fact = fact(n)
res = 0 #number of times
#n_fact was divided by 10
#so far.
while n_fact % 10 == 0:
n_fact //= 10
res += 1
return res
if __name__ == '__main__':
n = 20
print("The number of trailing zeros in", n, "factorial is", trailing_zerosA(n))
Here is another approach. Observe that
$n! = 1\times 2\times 3\times ... \times n = a\times 10^k$. If $k$ is as large as it can be, then $k$ is the number of trailing zeros in $n!$.
Now, we also have $n! = a\times 2^k\times 5^k$. So if we find out how many factors of $5$ there are in the product $n! = 1\times 2\times 3\times ... \times n$, we can find the number of trailing zeros. Let's do it factor-by-factor.
First, let's write a function that finds how many factors of factor there are in an arbitrary number m.
def count_factor(m, factor):
'''Return k s.t. k is the largest number s.t. m = factor^k * a
Arguments
m, factor -- positive integers
'''
res = 0 #number of times
#m was divided by factor
#so far.
while m % factor == 0:
m //= factor
res += 1
return res
The idea is to count up the number of factors of 5 in 1, 2, 3, 4, ...., n, and add them all up. Here is how this would go for $31!$:
1: 0 factors of 5
2: 0 factors of 5
3: 0 factors of 5
4: 0 factors of 5
5: 1 factors of 5
6: 0 factors of 5
7: 0 factors of 5
8: 0 factors of 5
9: 0 factors of 5
10: 1 factors of 5
11: 0 factors of 5
12: 0 factors of 5
13: 0 factors of 5
14: 0 factors of 5
15: 1 factors of 5
16: 0 factors of 5
17: 0 factors of 5
18: 0 factors of 5
19: 0 factors of 5
20: 1 factors of 5
21: 0 factors of 5
22: 0 factors of 5
23: 0 factors of 5
24: 0 factors of 5
25: 2 factors of 5
26: 0 factors of 5
27: 0 factors of 5
28: 0 factors of 5
29: 0 factors of 5
30: 1 factors of 5
31: 0 factors of 5
In total, we have 1+1+1+1+2+1=7 factors of 5, which is also the number of 0's
def trailing_zerosB(n):
res = 0
#Compute the total number of factors of 5 in all of
#1, 2, 3, ..., n
for i in range(1, n+1):
res += count_factor(i, 5)
return res
if __name__ == '__main__':
n = 20
print("The number of trailing zeros in", n, "factorial is", trailing_zerosA(n))
n = 31
print("The number of trailing zeros in", n, "factorial is", trailing_zerosA(n))
Why bother with all this? Because, as we saw in class, computing $n!$ for large $n$ is very computationally expensive, since it involves multiplying huge numbers. The second method is much more efficient.
trailing_zerosA()¶Note that we can now refactor trailing_zerosA(). That is, we can rewrite it to reuse a helper function:
def trailing_zerosA(n):
'''Return the number of trailing zeros in n!
Arguments:
n - a positive integer
'''
n_fact = fact(n)
return count_factor(n_fact, 10)
if __name__ == '__main__':
n = 20
print("The number of trailing zeros in", n, "factorial is", trailing_zerosA(n))