reduce vs. for loop

Fernando Pérez fperez528 at yahoo.com
Wed Mar 27 01:33:10 EST 2002


Tim Hochberg wrote:

> Try this out! For n=100, it's only twice as fast, but try it for n=1000, or
> n=2000 and it really shines.... (I think it even gives the right answer,
> which is a plus.)

Well, 

In [22]: def fact4(n):
   ....:     from sys import maxint
   ....:     mult = 1
   ....:     result = 1L
   ....:     subresult = 1
   ....:     max_sub_result = maxint / n
   ....:     for mult in range(n+1):
   ....:         subresult *= mult
   ....:         if subresult > max_sub_result:
   ....:             result *= subresult
   ....:             subresult = 1
   ....:     return result * subresult
   ....:

In [23]: fact4 10
-------> fact4 (10)
Out[23]: 0L

That doesn't look right to me.

It's hard to say what the op was after, but in general when you need large n 
factorials it's rare to truly need exact results. If you can live with an 
approximate answer (good to 10 sig figs or so), then a constant-time solution 
is vastly preferrable. Note that this is far from optimal, it's a 3 minute 
port of the standard routine from NumRecipes. But it will do in a pinch:

from Numeric import *
#-----------------------------------------------------------------------------
def gammln(x):
    """Return the natural log of gamma(x)"""

    cof = array([76.18009172947146,-86.50532032941677,
                 24.01409824083091,-1.231739572450155,
                 0.1208650973866179e-2,-0.5395239384953e-5],Float)

    y = arange(x+1,x+7,typecode = Float)
    tmp = x+5.5
    tmp -= (x+0.5)*log(tmp)
    ser = 1.000000000190015 + sum(cof/y)
    return -tmp+log(2.5066282746310005*ser/x)

def fact5(n):
    return long(round(exp(gammln(n+1))))
#-----------------------------------------------------------------------------

This solution obviously has two limitations:

- not exact, but quite good. Look at a numerical comparison with an exact (but 
super-slow) solution:

def fact6(n):
  return  reduce(operator.mul, range(1,n+1), 1L )

In [27]: fact5 50
-------> fact5 (50)
Out[27]: 30414093203509286326338827883471693198716826006464232959556714496L

In [28]: fact6 50
-------> fact6 (50)
Out[28]: 30414093201713378043612608166064768844377641568960512000000000000L

In [29]: 1.0*(_28-_27)/_27  # force float div
Out[29]: -5.9048555901676581e-11

So that's pretty good numerically.

- can't go outside the float range.

In most 'typical' cases, neither of these is a problem (since you can write 
most large n problems in terms of logs anyway, which is numerically much 
saner).

Cheers,

f.



More information about the Python-list mailing list