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) 6 >>> npr(100, 20) 1303995018204712451095685346159820800000 """ 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) 3 >>> ncr(100, 20) 535983370403809682970 >>> ncr(100000, 1000) == ncr(100000, 99000) True """ assert 0 <= r <= n if r > n // 2: r = n - r return npr(n, r) // factorial(r)
Just remember: If the order doesn't matter, it is a combination. If the order does matter it is a permutation.
The formula for permutations and combinations are related as: nCr = nPr/r!
Every topic in Mathematics is easy if you practice and understand its logic. Permutation and combination is a topic that requires logical thinking. Permutation and combination topic is easier as compared to other topics in mathematics such as calculus.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With