Skip to main content
Spaces around operators, per PEP-8
Source Link
Toby Speight
  • 81.8k
  • 14
  • 101
  • 309
if n==1 or n==0:
    return 1

That looks incorrect. 0! and 1! are both equal to 1, which has no trailing zeros, so we should be returning 0 there. If we had some unit tests, this would be more apparent.


We don't need to carry all the trailing zeros with us as we multiply; we can divide by ten whenever we have the opportunity, rather than waiting until the end:

    count = 0
    while n>1n > 1:
        fact=n*factfact *= n
        n=nn -= 1
        while fact%10==0fact % 10 == 0:
            count+=1count += 1
            fact=factfact = fact // 10

However, this only gets us a small gain. The real problem is that we have chosen a very inefficient algorithm. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n÷5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n÷5, then add n÷25, n÷125, n÷625, ... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(0)
    0
    >>> zeros(4)
    0
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
if n==1 or n==0:
    return 1

That looks incorrect. 0! and 1! are both equal to 1, which has no trailing zeros, so we should be returning 0 there. If we had some unit tests, this would be more apparent.


We don't need to carry all the trailing zeros with us as we multiply; we can divide by ten whenever we have the opportunity, rather than waiting until the end:

    count = 0
    while n>1:
        fact=n*fact
        n=n-1
        while fact%10==0:
            count+=1
            fact=fact//10

However, this only gets us a small gain. The real problem is that we have chosen a very inefficient algorithm. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n÷5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n÷5, then add n÷25, n÷125, n÷625, ... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(0)
    0
    >>> zeros(4)
    0
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
if n==1 or n==0:
    return 1

That looks incorrect. 0! and 1! are both equal to 1, which has no trailing zeros, so we should be returning 0 there. If we had some unit tests, this would be more apparent.


We don't need to carry all the trailing zeros with us as we multiply; we can divide by ten whenever we have the opportunity, rather than waiting until the end:

    count = 0
    while n > 1:
        fact *= n
        n -= 1
        while fact % 10 == 0:
            count += 1
            fact = fact // 10

However, this only gets us a small gain. The real problem is that we have chosen a very inefficient algorithm. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n÷5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n÷5, then add n÷25, n÷125, n÷625, ... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(0)
    0
    >>> zeros(4)
    0
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
Add 0 and 1 tests; more observations on the code
Source Link
Toby Speight
  • 81.8k
  • 14
  • 101
  • 309
if n==1 or n==0:
    return 1

The algorithmThat looks incorrect. 0! and 1! are both equal to 1, which has no trailing zeros, so we should be returning 0 there. If we had some unit tests, this would be more apparent.


We don't need to carry all the trailing zeros with us as we multiply; we can divide by ten whenever we have the opportunity, rather than waiting until the end:

    count = 0
    while n>1:
        fact=n*fact
        n=n-1
        while fact%10==0:
            count+=1
            fact=fact//10

However, this only gets us a small gain. The real problem is inefficientthat we have chosen a very inefficient algorithm. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n/5n÷5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n/5 andn÷5, then add n/25n÷25, n÷125, n÷625, n/125... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(0)
    0
    >>> zeros(4)
    0
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

The algorithm is inefficient. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n/5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n/5 and add n/25, n/125... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
if n==1 or n==0:
    return 1

That looks incorrect. 0! and 1! are both equal to 1, which has no trailing zeros, so we should be returning 0 there. If we had some unit tests, this would be more apparent.


We don't need to carry all the trailing zeros with us as we multiply; we can divide by ten whenever we have the opportunity, rather than waiting until the end:

    count = 0
    while n>1:
        fact=n*fact
        n=n-1
        while fact%10==0:
            count+=1
            fact=fact//10

However, this only gets us a small gain. The real problem is that we have chosen a very inefficient algorithm. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n÷5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n÷5, then add n÷25, n÷125, n÷625, ... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(0)
    0
    >>> zeros(4)
    0
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
Source Link
Toby Speight
  • 81.8k
  • 14
  • 101
  • 309

The algorithm is inefficient. We're multiplying by all the numbers in 2..n, but some mathematical insight helps us find a faster technique.

Observe that each trailing zero means a factor of 10, so we just need the lesser count of 2s or 5s in the prime factors of the factorial (which is the count of all 2s or 5s in all the numbers 1..n). We know there will be more 2s than 5s, since even numbers are much more common than multiples of 5, so we just need a way to count how many fives are in the factorial.

At first glance, that would appear to be n/5, since every 5th number is a multiple of 5. But we would undercount, because 25, 50, 75, ... all have two 5s in their prime factorisation, and 125, 250, 375, ... have three 5s, and so on.

So a simpler algorithm would be to start with n/5 and add n/25, n/125... We can do that recursively:

def zeros(n):
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

We can (and should) unit-test our function:

def zeros(n):
    """
    Return the number of trailing zeros in factorial(n)
    >>> zeros(5)
    1
    >>> zeros(24)
    4
    >>> zeros(25)
    6
    >>> zeros(625) - zeros(624)
    4
    """
    if n < 5:
        return 0
    return n // 5 + zeros(n // 5)

if __name__ == '__main__':
    import doctest
    doctest.testmod()