About Me

My photo
Northglenn, Colorado, United States
I'm primarily a BI Developer on the Microsoft stack. I do sometimes touch upon other Microsoft stacks ( web development, application development, and sql server development).

Friday, March 15, 2013

Just for kicks: Project Euler Problem #1

Just wanted to try a variety of methods to a solution. I wanted to do a variety of parallel, plinq, and straight forward logic test. I also wanted to implement a extension method and use func<> for better refactoring. As a test, I considered doing Euler's #1:
http://projecteuler.net/problem=1


Here are the several methods I created:
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.Threading;
  7.  
  8. namespace ProjectEuler
  9. {
  10.     /*
  11.      *   Q: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
  12.      *      Find the sum of all the multiples of 3 or 5 below 1000.
  13.      *
  14.      *   A: 233168
  15.      *   
  16.      *   Results:
  17.                     Method 1 Avg Time:  0.000007768
  18.                     Method 2a Avg Time: 0.000040169
  19.                     Method 2b Avg Time: 0.000029314
  20.                     Method 2c Avg Time: 0.000347431
  21.                     Method 2d Avg Time: 0.000175544
  22.                     Method 3a Avg Time: 0.00017445
  23.                     Method 3b Avg Time: 0.000144669
  24.                     Method 3c Avg Time: 0.002392947
  25.                     Method 3d Avg Time: 0.001309331
  26.      *   
  27.      */
  28.     class Problem1
  29.     {
  30.         int[] items = Enumerable.Range(1, 999).ToArray();
  31.         ReaderWriterLockSlim rw = new ReaderWriterLockSlim();
  32.         int sum = 0;
  33.  
  34.         //KISS Logic
  35.         public int RunMethod1()
  36.         {
  37.             sum = 0;
  38.  
  39.             foreach(int s in items)
  40.             {
  41.                 if (s % 3 == 0 || s % 5 == 0)
  42.                 {
  43.                     sum += s;
  44.                 }
  45.             };
  46.  
  47.             return sum;
  48.         }
  49.  
  50.         //Lazy Method
  51.         public int RunMethod2a()
  52.         {
  53.             return items.Where(n => n % 3 == 0 || n % 5 == 0).Sum();
  54.         }
  55.  
  56.         //Lazy Method on the fly
  57.         public int RunMethod2b()
  58.         {
  59.             return Enumerable.Range(1, 999).Where(n => n % 3 == 0 || n % 5 == 0).Sum();
  60.         }
  61.  
  62.         //Parallel Lazy Method
  63.         public int RunMethod2c()
  64.         {
  65.             return items.AsParallel().Where(n => n % 3 == 0 || n % 5 == 0).Sum();
  66.         }
  67.  
  68.         //Parallel Lazy Method on the fly
  69.         public int RunMethod2d()
  70.         {
  71.             return Enumerable.Range(1, 999).AsParallel().Where(n => n % 3 == 0 || n % 5 == 0).Sum();
  72.         }
  73.  
  74.         //Basic Parallel Method
  75.         public int RunMethod3a()
  76.         {
  77.             List<int> multOf3Or5 = new List<int>();
  78.  
  79.             Parallel.ForEach(items, s =>
  80.                 {
  81.                     if (s % 3 == 0 || s % 5 == 0)
  82.                     {
  83.                         lock (multOf3Or5)
  84.                         {
  85.                             multOf3Or5.Add(s);
  86.                         }
  87.                     }
  88.                 }
  89.             );
  90.  
  91.             return multOf3Or5.Sum();
  92.         }
  93.  
  94.         //Parallel it, but slice the work up
  95.         public int RunMethod3b()
  96.         {
  97.             List<int> multOf3Or5 = new List<int>();
  98.  
  99.             Parallel.ForEach(items.Slice(100), s =>
  100.             {
  101.                 foreach (int item in s)
  102.                 {
  103.                     if (item % 3 == 0 || item % 5 == 0)
  104.                     {
  105.                         lock (multOf3Or5)
  106.                         {
  107.                             multOf3Or5.Add(item);
  108.                         }
  109.                     }
  110.                 }
  111.             });
  112.  
  113.             return multOf3Or5.Sum();
  114.         }
  115.  
  116.  
  117.         //Basic Parallel Method just sum it
  118.         public int RunMethod3c()
  119.         {
  120.             sum = 0;
  121.  
  122.             Parallel.ForEach(items, s =>
  123.             {
  124.                 if (s % 3 == 0 || s % 5 == 0)
  125.                 {
  126.                     rw.EnterWriteLock();
  127.                     sum += s;
  128.                     rw.ExitWriteLock();
  129.                 }
  130.             });
  131.  
  132.             return sum;
  133.         }
  134.  
  135.         //Basic Parallel Method just slice & sum it
  136.         public int RunMethod3d()
  137.         {
  138.             sum = 0;
  139.             
  140.             Parallel.ForEach(items.Slice(100), s =>
  141.             {
  142.                 foreach (int item in s)
  143.                 {
  144.                     if (item % 3 == 0 || item % 5 == 0)
  145.                     {
  146.                         rw.EnterWriteLock();
  147.                         sum += item;
  148.                         rw.ExitWriteLock();
  149.                     }
  150.                 }
  151.             });
  152.  
  153.             return sum;
  154.         }
  155.     }
  156. }

Method 1: is the straight forward logic I would have used, and seems to be the quickest.
Method 2s: is probably something I would use as well, and to clean code up.
Method 3s: using Parallel programming, doesn't always speed things up. Depends on how much work needs to be done. I used an Extension method called Slice which expands the use of enumerable. I mainly got the idea to do this after reading about extention methods from http://www.blackrabbitcoder.net/archive/2013/03/08/c.net-little-wonders-extension-methods-demystified.aspx

For the Slice Extension, I used this:
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace ProjectEuler
  7. {
  8.     public static class EnumerableExtensions
  9.     {
  10.         //Source and Inspiration: http://www.blackrabbitcoder.net/archive/2013/03/08/c.net-little-wonders-extension-methods-demystified.aspx
  11.  
  12.         public static IEnumerable<IEnumerable<T>> Slice<T>(this IEnumerable<T> source, int size)
  13.         {
  14.             // can't slice null sequence
  15.             if (source == null) throw new ArgumentNullException("source");
  16.             if (size < 1) throw new ArgumentOutOfRangeException("size", "The size must be positive.");
  17.  
  18.             // force into a list to take advantage of direct indexing. Could also force into an
  19.             // array, use LINQ grouping, do a Skip()/Take(), etc...
  20.             var sourceList = source.ToList();
  21.             int current = 0;
  22.      
  23.             // while there are still items to "slice" off, keep going
  24.             while (current < sourceList.Count)
  25.             {
  26.                 // return a sub-slice using an iterator for deferred execution
  27.                 yield return sourceList.GetRange(current, Math.Min(size, sourceList.Count - current));
  28.                 current += size;
  29.             }
  30.         }
  31.     }
  32. }

The Main program. I hate repeating myself, so after some refactoring I came up with this to use delegates.
Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Diagnostics;
  6.  
  7. namespace ProjectEuler
  8. {
  9.     class Program
  10.     {
  11.         public static Stopwatch sw = new Stopwatch();
  12.         public delegate int fPointer();
  13.  
  14.         static void Main(string[] args)
  15.         {
  16.             //Initialize Problem(s)
  17.             Problem1 p1 = new Problem1();
  18.  
  19.             Func<fPointer,int, double> timeIt = (d,runCount) =>
  20.             {
  21.                 List<double> times = new List<double>();
  22.  
  23.                 for (int i = 0; i < runCount; i++)
  24.                 {
  25.                     sw.Reset();
  26.                     sw.Start();
  27.                     d();
  28.                     sw.Stop();
  29.                     times.Add(sw.Elapsed.TotalSeconds);
  30.                 }
  31.                 return times.Average();
  32.             };
  33.  
  34.             //Get Times of Running Methods
  35.             Console.WriteLine("Method 1 Avg Time: " + timeIt(p1.RunMethod1,100));
  36.             Console.WriteLine("Method 2a Avg Time: " + timeIt(p1.RunMethod2a, 100));
  37.             Console.WriteLine("Method 2b Avg Time: " + timeIt(p1.RunMethod2b, 100));
  38.             Console.WriteLine("Method 2c Avg Time: " + timeIt(p1.RunMethod2c, 100));
  39.             Console.WriteLine("Method 2d Avg Time: " + timeIt(p1.RunMethod2d, 100));
  40.             Console.WriteLine("Method 3a Avg Time: " + timeIt(p1.RunMethod3a, 100));
  41.             Console.WriteLine("Method 3b Avg Time: " + timeIt(p1.RunMethod3b, 100));
  42.             Console.WriteLine("Method 3c Avg Time: " + timeIt(p1.RunMethod3c, 100));
  43.             Console.WriteLine("Method 3d Avg Time: " + timeIt(p1.RunMethod3d, 100));
  44.  
  45.             Console.ReadKey();
  46.         }
  47.     }
  48. }

No comments: