4

I need to calculate n!/(n-r)!r! in C#. It's easy to calculate using the factorial function for small numbers but when the number gets bigger like 100, it doesn't work. Is there any other way with which we can calculate combinations for larger numbers?

DanM7
  • 2,203
  • 3
  • 28
  • 46
Jay
  • 373
  • 8
  • 22
  • What are you using to store the number? In 100 choose 10 the number is on the order of 10^13 so normal integers/longs run out of space rather quickly. You could try `BigInteger` instead but I don't know how big they can get. You could also make an n bit array and save the number as a binary number but setting the array's cells but that will require more computing. – twain249 Mar 08 '12 at 15:10
  • 1
    What does "It doesn't work fine." mean? – cadrell0 Mar 08 '12 at 15:10
  • the factorial gets big and the int64 does'nt accomodate it – Jay Mar 08 '12 at 15:17

4 Answers4

17

First off, I note that you are attempting to calculate the binomial coefficient, so let's call it that.

Here are a number of ways to do the calculation. If you use BigInteger you do not have to worry about overflow:

Method one: use factorial:

static BigInteger Factorial(BigInteger n)
{
    BigInteger f = 1;
    for (BigInteger i = 2; i <= n; ++i)
        f = f * i;
    return f;
}

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    return Factorial(n) / (Factorial(n-k) * Factorial(k));
}

Method two: use recursion:

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    if (n == 0) return 1;
    if (k == 0) return 0;
    return BinomialCoefficient(n-1, k-1) + BinomialCoefficient(n-1, k)
}

This method however is not fast unless you memoize the result.

Method Three: be more clever about minimizing the number of multiplications, and dividing early. This keeps the numbers small:

static BigInteger BinomialCoefficient(BigInteger n, BigInteger k)
{
    // (n C k) and (n C (n-k)) are the same, so pick the smaller as k:
    if (k > n - k) k = n - k;
    BigInteger result = 1;
    for (BigInteger i = 1; i <= k; ++i)
    {
        result *= n - k + i;
        result /= i;
    }
    return result;
}

So for example if you were computing (6 C 3), instead of computing (6 x 5 x 4 x 3 x 2 x 1) / ( (3 x 2 x 1) x (3 x 2 x 1)), you compute (((4 / 1) * 5) / 2) * 6) / 3, which keeps the numbers small if possible.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Great answer. Although the last implementation returns the wrong result for BC(0,10) ... it returns 1 when it should return 0. Adding the same check `if( k == 0 )...` as you have above resolves that. – LBushkin Mar 09 '12 at 02:59
  • @LBushkin I think it's method two that's broken. `k==0` should give a result of `1`. | Very interesting that method 3 works. It's not obvious to me that the division by `i` never leaves a remainder. – CodesInChaos Mar 10 '12 at 11:03
  • 3
    @CodeInChaos: It becomes obvious when you think like this: Suppose we have `(((n) / 1) * (n+1)) / 2`. Clearly either n or n+1 is divisible by two. Suppose we have `((((n) / 1) * (n+1)) / 2) * (n+2) / 3` -- we've already taken care of the 2. Clearly one of n, n+1 or n+2 is divisible by 3. And so on. Every time, we are dividing by a value that *must* appear as a factor in the current or a previous term. – Eric Lippert Mar 13 '12 at 15:16
5

Following what Eric said, dividing early helps a great deal, you can improve that by dividing from high to low. This way you can calculate any result as long as the endresult fits in a Long. Here's the code I use (apologize for the Java, but converting should be easy) :

public static long binomialCoefficient(int n, int k) {
   // take the lowest possible k to reduce computing using: n over k = n over (n-k)
   k = java.lang.Math.min( k, n - k );

   // holds the high number: fi. (1000 over 990) holds 991..1000
   long highnumber[] = new long[k];
   for (int i = 0; i < k; i++)
      highnumber[i] = n - i; // the high number first order is important
   // holds the dividers: fi. (1000 over 990) holds 2..10
   int dividers[] = new int[k - 1];
   for (int i = 0; i < k - 1; i++)
      dividers[i] = k - i;

   // for every divider there always exists a highnumber that can be divided by 
   // this, the number of highnumbers being a sequence that equals the number of 
   // dividers. Thus, the only trick needed is to divide in reverse order, so 
   // divide the highest divider first trying it on the highest highnumber first. 
   // That way you do not need to do any tricks with primes.
   for (int divider: dividers) 
      for (int i = 0; i < k; i++)
         if (highnumber[i] % divider == 0) {
            highnumber[i] /= divider;
            break;
         }

   // multiply remainder of highnumbers
   long result = 1;
   for (long high : highnumber)
      result *= high;
   return result;
}
Jeroen Vuurens
  • 1,171
  • 1
  • 9
  • 10
0

I think this will be efficient
It is O(k)

notice n! / r! the r! just cancels out the last r of n
so 7 3

7 x 6 x 5 x 4 x 3 x 2 x 1 
over 
            4 x 3 x 2 x 1 

public static uint BinomialCoeffient(uint n, uint k)
{
    if (k > n)
        return 0;

    uint c = n;
    for (uint i = 1; i < k; i++)
    {
        c *= n - i;
        c /= i + 1;
    }
    return c;
}
paparazzo
  • 44,497
  • 23
  • 105
  • 176
0

For .net 4.0 and larger use class BigInteger instead of int/long

For other .net use custom big number class, for example from IntX: http://www.codeplex.com/IntX/

Serj-Tm
  • 16,581
  • 4
  • 54
  • 61