Bill Blogs in C#

Bill Wagner discusses C#, LINQ, and other items of interest

Euler Problem 10

Patrick posted his solution earlier this week. I figure it was time to add mine.

Also, Octavio Hernandez pointed out in the comments to problem 8 that he had discussed similar code constructs in his blog a while back.

Problem 10 asks for the sum of all primes less than 2 million. It's also one line of LINQ code:

   1: var number = Common.Primes.GeneraePrimes().TakeWhile((n) => n < 2000000).Sum();
   2: Console.WriteLine(number);

As Patrick pointed out, this will cause an overflow unless you make some changes to the GeneratePrimes() method so that it returns a sequence of longs instead of a sequence of ints.

I also wasn't thrilled with the fact that it was taking a few minutes to finish instead of seconds.  That needed some fixing.

George Byrkit pointed out that the math can be optimized by recognizing that the highest factor you need to check for any N is Sqrt(N).

Sadly, the current .NET Framework does not contain methods that do the check efficiently. George suggested using List<T>FindAll() to find only those numbers smaller than the target. That method is a member of List and creates a new List object. Yuck. Also, it doesn't perform lazy evaluation, and it checks the entire list.

But now that the numbers have gotten much larger, it's worth the work to create my own version of those methods that speed this up.

Here's the new version of IsPrime:

 

   1: public static IEnumerable<long> GeneratePrimes()
   2: {
   3:     var range = Enumerable.Range(2, int.MaxValue-1);
   4:     var answers = from number in range
   5:                   where number.IsPrime()
   6:                   select (long)number;
   7:  
   8:     return answers;
   9: }

The only change here is to cast the prime number to a long before returning.

IsPrime gets a slightly larger makeover:

 

   1: private static bool IsPrime(this int number)
   2: {
   3:     bool isPrime = knownPrimes.TakeWhile(n => n*n <= number).
   4:         TrueForAll(n => number % n != 0);                   
   5:     if (isPrime)
   6:         knownPrimes.Add(number);
   7:     return isPrime;
   8: }

The astute reader will immediately notice that I'm calling TrueForAll using an IEnumerable<int> instead of a List<int>. That's not supposed to compile. And in fact, it doesn't, until I write my own version of TrueForAll:

   1: public static bool TrueForAll<T>(this IEnumerable<T> sequence, Predicate<T> test)
   2: {
   3:     foreach (T item in sequence)
   4:         if (!test(item))
   5:             return false;
   6:     return true;
   7: }

Note that this is typed for IEnumerable<T>, so the compiler will use this version on sequences that are not List<T>, but will still use the base framework version for all List<T> types.

And, now it produces the correct answers in under a minute.

Published Sun, Aug 31 2008 1:05 PM by wwagner

Comments

# re: Euler Problem 10@ Sunday, August 31, 2008 7:52 PM

Bravo, Bill!

And using this technique, (TakeWhile vs FindAll), Euler #7, my version, runs in approx .29 seconds (vs 20 seconds with my original attempt).  That's ~10 times faster than Bill's original implementation (about 2.5 seconds on my machine.)  I had only tried a simple attempt, as work is a bit overbearing at the moment.

I always learn a lot from Bill and his books.  It's wonderful to be able to work interactively on such problems with a 'master' and improve one's own skills (such as via a website like this).  I knew the flaw in my original approach (creating a new list after traversing the entire KnownPrimes list, for each potential prime being tested).

One of the more difficult things for me, an 'only programmer' at my employer (and 57, with many years of programming experience), is who are my peers, and how do I reach (out to) them to discuss the problems/issues that I run into, and how do I solve these problems?  It's so wonderful to have people like Bill in the Ann Arbor (and online) programming community.  The Ann Arbor Computing Society, some of the .Net user groups in the area, and contacts made thru both types of groups, constitute the peers that I have access to.

I'll certainly add TakeWhile and extending TrueForAll (and other methods) to my 'bag of tricks'.

by george byrkit

# re: Euler Problem 10@ Wednesday, September 03, 2008 6:25 AM

futher improvements

instead of

var range = Enumerable.Range(2, int.MaxValue-1);

you can loop union of 2 + odd numbers

odd numbers :

Enumerable.Range(1,int.MaxValue-)/2).Select(x =>  2 * x + 1);