Factorial function - design and test
I'm trying to nail down some interview questions, so I stared with a simple one.
Design the factorial function.
This function is a leaf (no dependencies - easly testable), so I made it static inside the helper class.
public static class MathHelper
{
public static int Factorial(int n)
{
Debug.Assert(n >= 0);
if (n < 0)
{
throw new ArgumentException("n cannot be lower that 0");
}
Debug.Assert(n <= 12);
if (n > 12)
{
throw new OverflowException("Overflow occurs above 12 factorial");
}
int factorialOfN = 1;
for (int i = 1; i &l开发者_JS百科t;= n; ++i)
{
//checked
//{
factorialOfN *= i;
//}
}
return factorialOfN;
}
}
Testing:
[TestMethod]
[ExpectedException(typeof(OverflowException))]
public void Overflow()
{
int temp = FactorialHelper.MathHelper.Factorial(40);
}
[TestMethod]
public void ZeroTest()
{
int factorialOfZero = FactorialHelper.MathHelper.Factorial(0);
Assert.AreEqual(1, factorialOfZero);
}
[TestMethod]
public void FactorialOf5()
{
int factOf5 = FactorialHelper.MathHelper.Factorial(5);
Assert.AreEqual(5*4*3*2*1,factOf5);
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void NegativeTest()
{
int factOfMinus5 = FactorialHelper.MathHelper.Factorial(-5);
}
I have a few questions:
- Is it correct? (I hope so ;) )
- Does it throw right exceptions?
- Should I use checked context or this trick ( n > 12 ) is ok?
- Is it better to use uint istead of checking for negative values?
- Future improving: Overload for long, decimal, BigInteger or maybe generic method?
Thank you
It looks right to me, but it would be inefficient with larger numbers. If you're allowing for big integers, the number will keep growing with each multiply, so you would see a tremendous (asymptotically better) increase in speed if you multiplied them hierarchically. For example:
bigint myFactorial(uint first, uint last)
{
if (first == last) return first;
uint mid = first + (last - first)/2;
return myFactorial(first,mid) * myFactorial(1+mid,last);
}
bigint factorial(uint n)
{
return myFactorial(2,n);
}
If you really want a fast factorial method, you also might consider something like this:
- Factor the factorial with a modified Sieve of Eratosthenes
- Compute the powers of each prime factor using a fast exponentiation algorithm (and fast multiplication and square algorithms)
- Multiply all the powers of primes together hierarchically
- Yes, it looks right
- The exceptions seem OK to me, and also as an interviewer, I can't see myself being concerned there
- Checked. Also, in an interview, you'd never know that 12 just happened to be the right number.
- Uint. If you can enforce something with a signature instead of an exception, do it.
- You should just make it long (or bigint) and be done with it (int is a silly choice of return types here)
Here are some follow-up questions I'd ask if I were your interviewer:
- Why didn't you solve this recursively? Factorial is a naturally recursive problem.
- Can you add memoization to this so that it does a faster job computing 12! if it's already done 11!?
- Do you need the
n==0
case here?
As an interviewer, I'd definitely have some curveballs like that to throw at you. In general, I like the approach of practicing with a whiteboard and a mock interviewer, because so much of it is being nimble and thinking on your feet.
In the for cycle you can start with for (int i = 2...). Multiplying by 1 is quite useless. I would have throw a single ArgumentOutOfRangeException for both < 0 and > 12. The Debug.Assert will mask the exception when you are using your unit test (you would have to test it in Release mode).
精彩评论