You can have both performance and readability using the newly-introduced (.NET 4.0) method BigInteger.GreatestCommonDivisor
(a reference to System.Numerics
is required).
With the BigInteger
stuct, you gain an additional advantage: your code will work for arbitrarily large numbers (assuming sufficient memory).
var result = ClosedInterval(1, 20).LeastCommonMultiple();
What we are looking for is the least common multiple of all natural numbers in the interval [1, 20]
, so we'll need a method to generate them (closed means including both bounds and the numbers between):
static IEnumerable<BigInteger> ClosedInterval(BigInteger first, BigInteger last)
{
for (var i = first; i <= last; i++)
{
yield return i;
}
}
From Wikipedia, we know how to calculate the least common multiple of two numbers:
static BigInteger LeastCommonMultiple(BigInteger a, BigInteger b)
{
return (a * b) / BigInteger.GreatestCommonDivisor(a, b);
}
For an arbitrary number of least common multiples, we simply aggregate them using LINQ and package the logic in an extension method:
static BigInteger LeastCommonMultiple(this IEnumerable<BigInteger> divisors)
{
return divisors.Aggregate(LeastCommonMultiple);
}
The implementation calculates the correct result (232792560) in less than ten milliseconds.
In less than half a second, you can find out the answer for an input of [1, 20000]
; The solution is over eight thousand digits long and therefore somewhat outside the scope of this answer.
What's really awesome:
There is an implicit cast from
int
toBigInteger
, so you can pass integer literals as arguments toClosedInterval
The arithmetic operators are overloaded, so you can use the same syntax for dividing, multiplying, etc. as you would with the integral numeric types
BigInteger
is extremely optimized for speed (the only thing to wary of is that it is astruct
, and therefore exhibits different memory behaviour than reference types do).It turns out that the aggregation is highly parallelizable! Just using PLINQ like this:
static BigInteger LeastCommonMultiple(this IEnumerable<BigInteger> divisors) { return divisors.AsParallel().Aggregate(LeastCommonMultiple); }
leads to a performance improvement proportional to the number of cores in your machine. In my case, finding the least common multiple of all natural numbers between one and two hundred thousand completed in around six seconds (the sequential version took over 45).