Take the 2-minute tour ×

I have some code to count permutations and combinations, and I'm trying to make it work better for large numbers.

I've found a better algorithm for permutations that avoids large intermediate results, but I still think I can do better for combinations.

So far, I've put in a special case to reflect the symmetry of nCr, but I'd still like to find a better algorithm that avoids the call to factorial(r), which is an unnecessarily large intermediate result. Without this optimization, the last doctest takes too long trying to calculate factorial(99000).

Can anyone suggest a more efficient way to count combinations?

from math import factorial

def product(iterable):
    prod = 1
    for n in iterable:
        prod *= n
    return prod

def npr(n, r):
    Calculate the number of ordered permutations of r items taken from a
    population of size n.

    >>> npr(3, 2)
    >>> npr(100, 20)
    assert 0 <= r <= n
    return product(range(n - r + 1, n + 1))

def ncr(n, r):
    Calculate the number of unordered combinations of r items taken from a
    population of size n.

    >>> ncr(3, 2)
    >>> ncr(100, 20)
    >>> ncr(100000, 1000) == ncr(100000, 99000)
    assert 0 <= r <= n
    if r > n // 2:
        r = n - r
    return npr(n, r) // factorial(r)
share|improve this question

9 Answers 9

up vote 16 down vote accepted

if n is not far from r then using the recursive definition of combination is probably better, since xC0 == 1 you will only have a few iterations:

The relevant recursive definition here is:

nCr = (n-1)C(r-1) * n/r

This can be nicely computed using tail recursion with the following list:

[(n - r, 0), (n - r + 1, 1), (n - r + 2, 2), ..., (n - 1, r - 1), (n, r)]

which is of course easily generated in Python (we omit the first entry since nC0 = 1) by izip(xrange(n - r + 1, n+1), xrange(1, r+1)) Note that this assumes r <= n you need to check for that and swap them if they are not. Also to optimize use if r < n/2 then r = n - r.

Now we simply need to apply the recursion step using tail recursion with reduce. We start with 1 since nC0 is 1 and then multiply the current value with the next entry from the list as below.

from itertools import izip

reduce(lambda x, y: x * y[0] / y[1], izip(xrange(n - r + 1, n+1), xrange(1, r+1)), 1)
share|improve this answer
For a single nCr this is better, but when you have multiple nCr's (on the order of N) then the dynamic programming approach is better, even though it has a long setup time, since it won't overflow into a 'bignum' unless necessary. –  JPvdMerwe Jan 20 '10 at 6:39

Two fairly simple suggestions:

  1. To avoid overflow, do everything in log space. Use the fact that log(a * b) = log(a) + log(b), and log(a / b) = log(a) - log(b). This makes it easy to work with very large factorials: log(n! / m!) = log(n!) - log(m!), etc.

  2. Use the gamma function instead of factorial. You can find one in scipy.stats.loggamma. It's a much more efficient way to calculate log-factorials than direct summation. loggamma(n) == log(factorial(n - 1)), and similarly, gamma(n) == factorial(n - 1).

share|improve this answer
Good suggestion doing things in log space. Not sure what you mean by "for precision" though. Wouldn't using log-floats cause roundoff error for large numbers? –  Christian Oudard Jan 20 '10 at 14:38
@Gorgapor: I guess a clearer way of stating this is: "To avoid overflow". Edited. –  dsimcha Jan 20 '10 at 15:27
Note that this will not give exact results, due to the limited precision of floating-point numbers. –  starblue Jan 20 '10 at 19:58
@starblue: But you know the real answer has to be an integer, so if you do something like round(exp(logFactorial(n))), it will be exact for small n. For large n it may be inexact, but anything other than (slow) arbitrary precision would just be dead wrong. –  dsimcha Jan 20 '10 at 20:02
there is not much trouble in computing this for small n. the point is to compute this accurately for large n, and I'm already using arbitrary precision, because I'm using python longs. –  Christian Oudard Jan 19 '11 at 19:16

If you don't need a pure-python solution, gmpy might help (gmpy.comb is very fast).

share|improve this answer
thanks for the reference, that's a very good practical solution. this is more of a learning project for me though, and so i'm more interested in the algorithm than the practical result. –  Christian Oudard Jan 20 '10 at 14:35
For those who come to this answer some years after it was written, gmpy is now known as gmpy2. –  Bill Bell Jan 3 at 17:41

If you are computing N choose K (which is what I think you're doing with ncr), there is a dynamic programming solution that may be a lot faster. This will avoid factorial, plus you can keep the table if you want for later use.

Here is a teaching link for it:


I am unsure of how to better solve your first problem, though, sorry.

Edit: Here is the mock-up. There are some pretty hilarious off-by-one errors, so it can certainly stand some more clean up.

import sys
n = int(sys.argv[1])+2#100
k = int(sys.argv[2])+1#20
table = [[0]*(n+2)]*(n+2)

for i in range(1,n):
    table[i][i] = 1
for i in range(1,n):
    for j in range(1,n-i):
        x = i+j
        if j == 1: table[x][j] = 1
        else: table[x][j] = table[x-1][j-1] + table[x-1][j]

print table[n][k]
share|improve this answer
It seems that this implementation is O(n^2) while the tail recursion I laid out is O(n) as far as I can see. –  wich Jan 19 '10 at 20:23
It seems a different recursive definition is used. here n choose k = n-1 choose k-1 + n-1 choose k, while I used n choose k = n-1 choose k-1 * n/k –  wich Jan 19 '10 at 20:30
Indeed, such is the case, wich. I will shortly edit this post to include a quick python mock-up of the algorithm. Yours is significantly faster. I will leave my post here, in case if Gorgapor has some exotic machine in which multiplication requires hours. >.> –  agorenst Jan 19 '10 at 21:40
This might be O(N^2) but it precalculates all combination pairs of nCr, so if you are gonna use nCr a lot with a lot of different values, this will be faster, because lookups are O(1) and is less susceptable to overflows. For one value the O(N) algo is better though. –  JPvdMerwe Jan 20 '10 at 6:37

If your problem does not require knowing the exact number of permutations or combinations, then you could use Stirling's approximation for the factorial.

That would lead to code like this:

import math

def stirling(n):
    # http://en.wikipedia.org/wiki/Stirling%27s_approximation
    return math.sqrt(2*math.pi*n)*(n/math.e)**n

def npr(n,r):
    return (stirling(n)/stirling(n-r) if n>20 else

def ncr(n,r):    
    return (stirling(n)/stirling(r)/stirling(n-r) if n>20 else

# 6
# 1.30426670868e+39
# 3
# 5.38333246453e+20
share|improve this answer
the main problem with the factorial is the size of the result, not the time calculating it. also, the values of the result here are much bigger than can be accurately represented by a float value. –  Christian Oudard Jan 20 '10 at 14:34

There's a function for this in scipy which hasn't been mentioned yet: scipy.misc.comb. It seems efficient based on some quick timing results for your doctest (~0.004 seconds for comb(100000, 1000, 1) == comb(100000, 99000, 1)).

[While this specific question seems to be about algorithms the question is there a math ncr function in python is marked as a duplicate of this...]

share|improve this answer

Using xrange() instead of range() will speed things up slightly due to the fact that no intermediate list is created, populated, iterated through, and then destroyed. Also, reduce() with operator.mul.

share|improve this answer
sorry i wasn't clear, my code is python 3, not python 2. range in python 3 is the same as xrange in python 2. –  Christian Oudard Jan 19 '10 at 20:16

For N choose K you could use Pascals triangle. Basically you would need to keep array of size N around to compute all the N choose K values. Only additions would be required.

share|improve this answer
This is basically what Agor suggested, but it would be O(n^2). Since using multiplications and divisions is really not a problem anymore these days, using a different recursion relation one can make the algorithm O(n) as I described. –  wich Jan 19 '10 at 20:53

You could input two integers and import math library to find the factorial and then apply the nCr formula

import math
n,r=[int(_)for _ in raw_input().split()]
print f(n)/f(r)/f(n-r)

share|improve this answer

Your Answer


