1

I got stuck with a question in which I have first to find the factorial of a number and then return the number of digits obtained in the factorial. I wrote the program and it's working fine. But the time is 5.0015 sec and I have to do in 1 sec. How to reduce this?

Below is my program:

def factorial(n):
    fact = 1
    for y in xrange(1,n+1):
        fact = fact * y
    return fact

t = int(raw_input())
raw_input()
for x in xrange(t):
    n = int(raw_input())
    print len(str(factorial(n)))
Wolf
  • 9,679
  • 7
  • 62
  • 108
Sheesh Mohsin
  • 1,455
  • 11
  • 28
  • It won't change the time complexity, but it be a lot simpler and probably faster by using built-in functions: `len(str(math.factorial(n)))`. – interjay Mar 01 '15 at 15:13
  • I already tried this. But I have to reduce the time complexity – Sheesh Mohsin Mar 01 '15 at 15:14
  • First of all your code is not very clear. Why are you defining `length` if you are not using it? Why are you looping instead of doing just `print len(str(factorial(n)))`? – skamsie Mar 01 '15 at 15:27
  • @HerrActress, this is more than likely an online code problem where you have to use raw_input to take in the arguments – Padraic Cunningham Mar 01 '15 at 15:29
  • 1
    Even memozation won't get you to a better complexity (but it could improve speed). For an interesting take at the problem, you can review this question: http://stackoverflow.com/questions/9815252/why-is-math-factorial-much-slower-in-python-2-x-than-3-x – marianosimone Mar 01 '15 at 15:40
  • Hey Herr Actress, I have used length function to got the length manually but also tried with len (in built function) and just thought to show all the code, what i have tried to implement. that's why i didn't remove the length function. – Sheesh Mohsin Mar 01 '15 at 17:36

3 Answers3

4

Use the power of mathematics and avoid calculating the factorial entirely.

The base 10 logarithm of a number is the power to which 10 needs to be raised to be equal to that number. For example, math.log10(10) == 1, math.log10(11) == 1.0413926851582251, math.log10(100) == 2, etc.

This means that the smallest integer stictly greater than the log10 of a number, (that is, ceil(log10(n)) + (1 if log10(n).is_integer() else 0) ) is the number of digits in that number.

In addition, log10(a * b) = log10(a) + log10(b).

Which means that log10(factorial(n)) == sum(log10(a) for a in range(1, n+1))

Finally, the length of that factorial is:

math.ceil(sum(math.log10(a) for a in xrange(1, n+1)))

(more info here: http://en.wikipedia.org/wiki/Logarithm)

Max Noel
  • 8,810
  • 1
  • 27
  • 35
2

I suggest using Stirling's approximation for log(n!):

from math import ceil, log, pi

def log_fact(n):
    # using Stirling's approximation to calculate log10(n!)
    n = float(n)
    return (
        n * log(n)
        - n
        + log(2 * pi * n) / 2.
        + 1/(  12 * n   )
        - 1/( 360 * n**3)
        + 1/(1260 * n**5)
    ) / log(10)

At a guess your value for n is around 60000; testing I get

import math
n = 60000

%%timeit
# @SheeshMosin
len(str(factorial(n)))                                 # 3.93 s

%%timeit
# @MaxNoel
math.ceil(sum(math.log10(a) for a in range(1, n+1)))   # 16.1 ms
                                                       # (250 times faster)

%%timeit
# @HughBothwell
math.ceil(log_fact(n))                                 # 3.62 µs
                                                       # (1.08 million times faster)
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
-4

You could try making the function recursive.

vaultah
  • 44,105
  • 12
  • 114
  • 143
Klas. S
  • 650
  • 9
  • 21