3
\$\begingroup\$

I've been working on the Project Euler problems, so I created a GUI and a Solver class for all the code I've been creating. I'd like to share it with you guys and to get general feedback on my coding and how I chose to organize the class.

You can also give feedback on how I solved the Project Euler problems if you'd like. Be warned, I did nothing fancy. I just brute forced everything with nested loops, pretty much.

I think I'm proudest of my solution to #11, which is taking a giant array of ints, and multiplying n consecutive numbers together (columns, rows, diagonals) to find the greatest product. My solution can handle n consecutive numbers (I didn't use a magic number), and it checks every row, column, NW to SE diagonal, and NE to SW diagonal.

Problems 5 and 10 run the slowest. Problem 5 takes 5 seconds, problem 10 takes 3 seconds. The rest run in less than 1 second.

Project Euler GUI

using System;
using System.Windows.Forms;
using System.Diagnostics;

namespace ProjectEuler
{
    public partial class GUI : Form
    {
        public GUI()
        {
            InitializeComponent();
        }

        private void GUI_Load(object sender, EventArgs e)
        {
            ProblemList.Items.Add("Problem 1");
            ProblemList.Items.Add("Problem 2");
            ProblemList.Items.Add("Problem 3");
            ProblemList.Items.Add("Problem 4");
            ProblemList.Items.Add("Problem 5");
            ProblemList.Items.Add("Problem 6");
            ProblemList.Items.Add("Problem 7");
            ProblemList.Items.Add("Problem 8");
            ProblemList.Items.Add("Problem 10");
            ProblemList.Items.Add("Problem 11");
        }

        private void ProblemList_Click(object sender, EventArgs e)
        {
            string problemSelectedText = (string)ProblemList.SelectedItem;

            int problemNum = Convert.ToInt32((problemSelectedText).Substring(8, problemSelectedText.Length - 8));

            ProblemBox.Text = "Calculating solution. Please wait.";
            SolutionBox.Text = "Calculating solution. Please wait.";
            ExecutionTimeBox.Text = "Calculating solution. Please wait.";
            Refresh();

            Solver sol = new Solver(problemNum);
            ProblemBox.Text = sol.MyProblem;
            SolutionBox.Text = Convert.ToString(sol.MyAnswer);
            ExecutionTimeBox.Text = sol.MyTimeElapsed;
        }
    }
}

using System;
using System.Diagnostics;
using System.Collections.Generic;

namespace ProjectEuler
{
    class Solver
    {
        // NOTE: int goes up to 2 billion before overflowing
        // NOTE: long goes up to 9 quintillion before overflowing

        #region Variables
        public string MyProblem = "";
        public object MyAnswer = "";
        public string MyTimeElapsed;
        #endregion

        #region Public
        public Solver(int problemNum)
        {
            Dictionary<int, string> problems = new Dictionary<int, string>();
            problems.Add(1, "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.\r\n\r\nFind the sum of all the multiples of 3 or 5 below 1000.");
            problems.Add(2, "Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:\r\n\r\n1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...\r\n\r\nBy considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.");
            problems.Add(3, "The prime factors of 13195 are 5, 7, 13 and 29.\r\n\r\nWhat is the largest prime factor of the number 600851475143 ?");
            problems.Add(4, "A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.\r\n\r\nFind the largest palindrome made from the product of two 3-digit numbers.");
            problems.Add(5, "2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.\r\n\r\nWhat is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?");
            problems.Add(6, "The sum of the squares of the first ten natural numbers is,\r\n1^2 + 2^2 + ... + 10^2 = 385\r\n\r\nThe square of the sum of the first ten natural numbers is,\r\n(1 + 2 + ... + 10)^2 = 55^2 = 3025\r\n\r\nHence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is 3025 - 385 = 2640.\r\n\r\nFind the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum.");
            problems.Add(7, "By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.\r\n\r\nWhat is the 10 001st prime number?");
            problems.Add(8, "The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832.\r\n\r\n7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450\r\n\r\nFind the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product?");
            problems.Add(10, "The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.\r\n\r\nFind the sum of all the primes below two million.");
            problems.Add(11, "In the 20×20 grid below, four numbers along a diagonal line have been marked in red.\r\n\r\n08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08\r\n49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00\r\n81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65\r\n52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91\r\n22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80\r\n24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50\r\n32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70\r\n67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21\r\n24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72\r\n21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95\r\n78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92\r\n16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57\r\n86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58\r\n19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40\r\n04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66\r\n88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69\r\n04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36\r\n20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16\r\n20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54\r\n01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48\r\n\r\nThe product of these numbers is 26 × 63 × 78 × 14 = 1788696.\r\n\r\nWhat is the greatest product of four adjacent numbers in the same direction (up, down, left, right, or diagonally) in the 20×20 grid?");
            MyProblem = problems[problemNum];

            Stopwatch sw = new Stopwatch();
            sw.Start();
            switch (problemNum)
            {
                case 1:
                    MyAnswer = Solve1();
                    break;
                case 2:
                    MyAnswer = Solve2();
                    break;
                case 3:
                    MyAnswer = Solve3();
                    break;
                case 4:
                    MyAnswer = Solve4();
                    break;
                case 5:
                    MyAnswer = Solve5();
                    break;
                case 6:
                    MyAnswer = Solve6();
                    break;
                case 7:
                    MyAnswer = Solve7();
                    break;
                case 8:
                    MyAnswer = Solve8();
                    break;
                case 10:
                    MyAnswer = Solve10();
                    break;
                case 11:
                    MyAnswer = Solve11();
                    break;
            }
            sw.Stop();
            MyTimeElapsed = sw.Elapsed.ToString();
        }
        #endregion

        #region Solvers
        private int Solve1()
        {
            int SumOfMultiples = 0;

            // 0 is included in the natural number set, but the wording of the question means it is not in the solution set
            for (int i = 1; i < 1000; i++)
            {
                if (i % 3 == 0 || i % 5 == 0)
                {
                    SumOfMultiples += i;
                }
            }

            return SumOfMultiples;
        }

        private int Solve2()
        {
            // pre-calculated variables
            int LoopLength = 4000000;
            int SumOfFibonaccis = 2;
            int PreviousTerm1 = 1;
            int PreviousTerm2 = 2;

            // we'll let the loop calculate this variable
            int CurrentTerm = 0;

            while (CurrentTerm <= LoopLength)
            {
                CurrentTerm = PreviousTerm1 + PreviousTerm2;

                if (CurrentTerm <= LoopLength)
                {
                    if (IsEven(CurrentTerm))
                    {
                        SumOfFibonaccis += CurrentTerm;
                    }
                    PreviousTerm1 = PreviousTerm2;
                    PreviousTerm2 = CurrentTerm;
                }
            }

            return SumOfFibonaccis;
        }

        private long Solve3()
        {
            long NumberToCheck = 600851475143;
            long LargestPrimeFactorFound = 0;
            long SquareRoot = SquareRootRoundedUp(NumberToCheck);

            for (long i = 2; i <= SquareRoot; i++)
            {
                if (NumberToCheck % i == 0)
                {
                    if (IsPrime(i))
                    {
                        LargestPrimeFactorFound = i;
                    }
                }
            }

            return LargestPrimeFactorFound;
        }

        private int Solve4()
        {
            int LargestPalindromeFound = 0;

            for (int i = 100; i <= 999; i++)
            {
                for (int j = 100; j <= 999; j++)
                {
                    int ProductForThisIteration = i * j;

                    if (ProductForThisIteration > LargestPalindromeFound)
                    {
                        if (IsPalindrome(ProductForThisIteration) == true)
                        {
                            LargestPalindromeFound = ProductForThisIteration;
                        }
                    }
                }
            }

            return LargestPalindromeFound;
        }

        private int Solve5()
        {
            int smallestNumberDivisibleByXThruY = 0;
            bool numberDivisibleByXThruY = true;
            int x = 1;
            int y = 20;
            int numToTry = 10;

            while (smallestNumberDivisibleByXThruY == 0)
            {
                numberDivisibleByXThruY = true;

                for (int j = x; j <= y; j++)
                {
                    if (numToTry % j != 0)
                    {
                        numberDivisibleByXThruY = false;
                        break;
                    }
                }

                if (numberDivisibleByXThruY == true)
                {
                    smallestNumberDivisibleByXThruY = numToTry;
                }
                numToTry++;
            }

            return smallestNumberDivisibleByXThruY;
        }

        private int Solve6()
        {
            return SquareOfSums(1, 100) - SumOfSquares(1, 100);
        }

        private long Solve7()
        {
            return FindNthPrime(10001);
        }

        private long Solve8()
        {
            string numToCheck = "7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450";
            int numOfAdjacentNumbersToFind = 13;

            string currentStringToCheck = "";
            long productOfDigits = 0;
            long biggestProductFound = 0;

            for (int i = 0; i <= numToCheck.Length - numOfAdjacentNumbersToFind; i++)
            {
                currentStringToCheck = numToCheck.Substring(i, numOfAdjacentNumbersToFind);

                productOfDigits = MultiplyEachDigitOfNumberTogether(currentStringToCheck);

                if (productOfDigits > biggestProductFound)
                {
                    biggestProductFound = productOfDigits;
                }
            }

            return biggestProductFound;
        }

        private long Solve10()
        {
            return SumPrimesBelowN(2000000);
        }

        private long Solve11()
        {
            int[,] numsToCheck = new int[20, 20] {
                {8,2,22,97,38,15,0,40,0,75,4,5,7,78,52,12,50,77,91,8},
                {49,49,99,40,17,81,18,57,60,87,17,40,98,43,69,48,4,56,62,0},
                {81,49,31,73,55,79,14,29,93,71,40,67,53,88,30,3,49,13,36,65},
                {52,70,95,23,4,60,11,42,69,24,68,56,1,32,56,71,37,2,36,91},
                {22,31,16,71,51,67,63,89,41,92,36,54,22,40,40,28,66,33,13,80},
                {24,47,32,60,99,3,45,2,44,75,33,53,78,36,84,20,35,17,12,50},
                {32,98,81,28,64,23,67,10,26,38,40,67,59,54,70,66,18,38,64,70},
                {67,26,20,68,2,62,12,20,95,63,94,39,63,8,40,91,66,49,94,21},
                {24,55,58,5,66,73,99,26,97,17,78,78,96,83,14,88,34,89,63,72},
                {21,36,23,9,75,0,76,44,20,45,35,14,0,61,33,97,34,31,33,95},
                {78,17,53,28,22,75,31,67,15,94,3,80,4,62,16,14,9,53,56,92},
                {16,39,5,42,96,35,31,47,55,58,88,24,0,17,54,24,36,29,85,57},
                {86,56,0,48,35,71,89,7,5,44,44,37,44,60,21,58,51,54,17,58},
                {19,80,81,68,5,94,47,69,28,73,92,13,86,52,17,77,4,89,55,40},
                {4,52,8,83,97,35,99,16,7,97,57,32,16,26,26,79,33,27,98,66},
                {88,36,68,87,57,62,20,72,3,46,33,67,46,55,12,32,63,93,53,69},
                {4,42,16,73,38,25,39,11,24,94,72,18,8,46,29,32,40,62,76,36},
                {20,69,36,41,72,30,23,88,34,62,99,69,82,67,59,85,74,4,36,16},
                {20,73,35,29,78,31,90,1,74,31,49,71,48,86,81,16,23,57,5,54},
                {1,70,54,71,83,51,54,69,16,92,33,48,61,43,52,1,89,19,67,48}
            };
            int numOfProducts = 4;

            long currentProduct;
            long biggestProduct = 0;
            int currentCellValue;

            // up and down
            for (int col = 0; col < numsToCheck.GetLength(1); col++)
            {
                for (int row = 0; row < numsToCheck.GetLength(0) - numOfProducts + 1; row++)
                {
                    currentProduct = 0;

                    for (int num = 0; num < numOfProducts; num++)
                    {
                        currentCellValue = numsToCheck[row + num, col];

                        if (num == 0)
                        {
                            currentProduct = currentCellValue;
                        }
                        else
                        {
                            currentProduct *= currentCellValue;
                        }
                    }

                    if (currentProduct > biggestProduct)
                    {
                        biggestProduct = currentProduct;
                    }
                }
            }

            // left and right
            for (int row = 0; row < numsToCheck.GetLength(0); row++)
            {
                for (int col = 0; col < numsToCheck.GetLength(1) - numOfProducts + 1; col++)
                {
                    currentProduct = 0;

                    for (int num = 0; num < numOfProducts; num++)
                    {
                        currentCellValue = numsToCheck[row, col + num];

                        if (num == 0)
                        {
                            currentProduct = currentCellValue;
                        }
                        else
                        {
                            currentProduct *= currentCellValue;
                        }
                    }

                    if (currentProduct > biggestProduct)
                    {
                        biggestProduct = currentProduct;
                    }
                }
            }

            // diagonally NW to SE
            for (int row = 0; row < numsToCheck.GetLength(0) - numOfProducts + 1; row++)
            {
                for (int col = 0; col < numsToCheck.GetLength(1) - numOfProducts + 1; col++)
                {
                    currentProduct = 0;

                    for (int num = 0; num < numOfProducts; num++)
                    {
                        currentCellValue = numsToCheck[row + num, col + num];

                        if (num == 0)
                        {
                            currentProduct = currentCellValue;
                        }
                        else
                        {
                            currentProduct *= currentCellValue;
                        }
                    }

                    if (currentProduct > biggestProduct)
                    {
                        biggestProduct = currentProduct;
                    }
                }
            }

            // diagonally NE to SW
            for (int col = 0 + numOfProducts - 1; col < numsToCheck.GetLength(0); col++)
            {
                for (int row = 0; row < numsToCheck.GetLength(1) - numOfProducts + 1; row++)
                {
                    currentProduct = 0;

                    for (int num = 0; num < numOfProducts; num++)
                    {
                        currentCellValue = numsToCheck[row + num, col - num];

                        if (num == 0)
                        {
                            currentProduct = currentCellValue;
                        }
                        else
                        {
                            currentProduct *= currentCellValue;
                        }
                    }

                    if (currentProduct > biggestProduct)
                    {
                        biggestProduct = currentProduct;
                    }
                }
            }

            return biggestProduct;
        }
        #endregion

        #region Private
        // Code in this region might be useful in more than one problem, so I went to the trouble of making
        // a method for it.
        private string TrimFromBeginning(string str, int numOfCharsToTrim)
        {
            return str.Remove(0, numOfCharsToTrim);
        }

        private string TrimFromEnd(string str, int numOfCharsToTrim)
        {
            return str.Remove(str.Length - numOfCharsToTrim, numOfCharsToTrim);
        }

        private bool IsPrime(long NumToCheck)
        {
            // A prime number (or a prime) is a natural number greater than 1 that has no positive divisors
            // other than 1 and itself.

            // By definition, numbers 1 or less are not primes.
            if (NumToCheck <= 1)
            {
                return false;
            }
            // 2 is prime, but the loop below won't think so. We need to define it up here.
            else if (NumToCheck == 2)
            {
                return true;
            }

            long SquareRoot = SquareRootRoundedUp(NumToCheck);

            for (long i = 2; i <= SquareRoot; i++)
            {
                if (NumToCheck % i == 0)
                {
                    return false;
                }
            }

            return true;
        }

        private bool IsPalindrome(int NumToTest)
        {
            string StringOfNum = NumToTest.ToString();

            for (int i = 1; i <= StringOfNum.Length / 2; i++)
            {
                if (StringOfNum.Substring(i - 1, 1) != StringOfNum.Substring(StringOfNum.Length - i, 1))
                {
                    return false;
                }
            }

            return true;
        }

        private bool IsEven(int NumToTest)
        {
            if (NumToTest % 2 == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private long SquareRootRoundedUp(long NumToSquareRoot)
        {
            return Convert.ToInt64(Math.Ceiling(Convert.ToDecimal(Math.Sqrt(NumToSquareRoot))));
        }

        private int SumOfSquares(int firstNum, int lastNum)
        {
            int sum = 0;

            for (int i = firstNum; i <= lastNum; i++)
            {
                sum += Convert.ToInt32(Math.Pow(i, 2));
            }

            return sum;
        }

        private int SquareOfSums(int firstNum, int lastNum)
        {
            int sum = 0;

            for (int i = firstNum; i <= lastNum; i++)
            {
                sum += i;
            }

            return Convert.ToInt32(Math.Pow(sum, 2));
        }

        private long FindNthPrime(long n)
        {
            if (n < 1)
            {
                throw new ArgumentException("The number of the nth prime to find must be positive!");
            }

            long numToTry = 2;
            long currentPrime = 0;
            long currentPrimeCount = 0;

            while (currentPrimeCount < n)
            {
                if (IsPrime(numToTry))
                {
                    currentPrime = numToTry;
                    currentPrimeCount++;
                }

                numToTry++;
            }

            return currentPrime;
        }

        private long SumNPrimes(long n)
        {
            if (n < 0)
            {
                throw new ArgumentException("The number of primes to sum must be positive!");
            }

            long numToTry = 2;
            long currentSum = 0;
            long currentPrimeCount = 0;

            while (currentPrimeCount < n)
            {
                if (IsPrime(numToTry))
                {
                    currentSum += numToTry;
                    currentPrimeCount++;
                }

                numToTry++;
            }

            return currentSum;
        }

        private long SumPrimesBelowN(long n)
        {
            if (n < 1)
            {
                throw new ArgumentException("The number where we stop summing primes must be positive!");
            }

            long currentSum = 0;

            for (int i = 2; i < n; i++)
            {
                if (IsPrime(i))
                {
                    currentSum += i;
                }
            }

            return currentSum;
        }

        private long MultiplyEachDigitOfNumberTogether(string str)
        {
            long product = 0;

            for (int i = 0; i < str.Length; i++)
            {
                if (i == 0)
                {
                    product = GetOneDigitFromNumber(str, i);
                }
                else
                {
                    product *= GetOneDigitFromNumber(str, i);
                }
            }

            return product;
        }

        private long GetOneDigitFromNumber(string str, int position)
        {
            return Convert.ToInt64(str.Substring(position, 1));
        }
        #endregion
    }
}
\$\endgroup\$
6
\$\begingroup\$

As long as the helpers are neutral and atomic, I can't see a problem there.

IMO, overall your code is readable, and for the most part you have refactored the reusable helper functions out nicely.

  • Solve11 would be one exception requiring refactoring - there are a lot of different, and repeated concerns handled here which can be DRY'd out into helpers.

  • Often regarded as stylistic, but many favour the use of the conditional (ternary) operator over an if-branch when assigning the same variable, or returning a value of the same type from two branches. e.g. both branches of this (repeated) code assign to currentProduct:

 if (num == 0)
 {
     currentProduct = currentCellValue;
 }
 else
 {
     currentProduct *= currentCellValue;
 }

And can be abbreviated to:

 currentProduct = (num == 0) // Parens aren't actually needed
     ? currentCellValue
     : currentProduct * currentCellValue;
  • At some point you may find it unwieldy to put all of your Euler solutions in one class / one file. You can adopt a strategy like breaking them up into partial classes, or separate classes entirely (e.g. collate Prime related puzzles vs Palindromes etc).

  • Many of the methods (like SolveX and helpers like SumNPrimes) are simply mathematical functions and do not access and member fields and do not need this and can be marked static.

  • For brevity, you can simply echo the result of a boolean expression, i.e. replace:

 if (NumToTest % 2 == 0)
 {
     return true;
 }
 else
 {
     return false;
 }

With simply:

 return NumToTest % 2 == 0;
  • C# offers a number of shortcuts for compile-time collection initialization, including for Dictionaries. Also, although your code will probably run just the once, it is usually a good idea to extract immutable data initialization out of methods, and initialize it once-per-process, e.g.

private static readonly IReadOnlyDictionary<int, string> Problems = new Dictionary<int, string>
{
     {1, "foo"},
     {2, "bar"},
     ...
};
  • Possibly pedantic, but you could also encapsulate the "problems" to include the function itself in the Dictionary. You would however need all the SolveX functions to return the same type (and weak types like object or the dynamic type should be avoided). AFAIK all the Euler solutions are integral, so long seems reasonable, (Although from memory you might also need to make use of BigInteger at some point when long is insufficient)

private static readonly IDictionary<int, Tuple<string, Func<long>>> Problems 
= new Dictionary<int, Tuple<string, Func<long>>>
{
     {1, new Tuple<string, Func<long>>("foo", Solve1)},
     {2, new Tuple<string, Func<long>>("bar", Solve2)},
     ...
};

You can then replace the unwieldy (and unencapsulated) switch statement reasoning over the Dictionary's keys:

 switch (problemNum)
 {
      case 1:
           MyAnswer = Solve1();
           break;

To a simple Dictionary lookup:

 var problem = Problems[problemNum];

Which you can then invoke

 var solution = problem.Item2();

And if you don't trust the user input for problemNum, you could be more defensive with Dictionary.TryGetValue

(I've been slack with the Tuple which results in the horrid Item1 / Item2, but you could improve this with a custom strong class to replace the Tuple)

Also, since C# is starting to assume many of the features of FP languages, you might also want to look at using LINQ, which will again reduce the number of lines of code (especially around adding items to lists, filtering, grouping, projecting new values etc).

Possibly more advanced, but another really useful tool for solving Euler type problems are generators. C# has the yield keyword, which allows for lazy generation (and iteration) of items. (e.g. one of your helper functions can lazily generate a IEnumerable<long> of Prime Numbers, which your solutions can reason over with LINQ).

Good luck with your Project Euler endeavors!

One last thought - you might also want to formulate your SolveX methods as Unit Tests (e.g. with NUnit). The idea here is that once you have the correct answer, that you can then Assert the correctness of your solution, and thus encourage you to do further refactorings and enhancements to a solution. You've already made use of a Stopwatch, so you are already thinking along these lines :)

\$\endgroup\$
  • 1
    \$\begingroup\$ 1) Thanks for planting to seeds for all these advanced concepts to explore. LINQ, Generators, Unit Tests, etc. I'll research those and try to incorporate them into this code for practice. 2) Thanks for pointing out the boolean simplifications (? : and return ==). The more readable and concise that code is, the better, I say. I'll make those changes. \$\endgroup\$ – AdmiralAdama Apr 6 '16 at 7:25
  • 2
    \$\begingroup\$ It is perfectly fine to share Project Euler answers so long as the title accurately reflects the problem number so the solution is not spoiled. The OP doesn't satisfy that, but to imply that sharing Euler solutions should not be shared is disingenuous (and a drop of the ball by the person who wrote that excerpt). \$\endgroup\$ – Dan Pantry Apr 6 '16 at 8:39
  • \$\begingroup\$ If the solveX methods were removed, your answer would be invalidated and we would rollback the edit. Code Review requires working code; not posting the solution would mean all project-euler questions are off topic! \$\endgroup\$ – Pimgd Apr 6 '16 at 8:40
  • \$\begingroup\$ As it stands, unfortunately anyone running OP's code gets the answer to the question. Provided OP has refactored the grunt work into neutral helper functions, we can review these helpers (which is where the action is), without compromising PE's answers. The SolveX methods would be trivial and uninteresting for review anyway, e.g. var result = GetPrimeNumber(999); Assert.AreEqual(7907, result). It is the helper functions where all the value is added. @Quill's edit raises a good point however - PE's "About" page only shows the warning I referenced about not sharing the answer if you log in. \$\endgroup\$ – StuartLC Apr 6 '16 at 9:24
1
\$\begingroup\$

Improvements.

  1. I would recommend an Interface implementation instead SolveX(...) methods.
  2. Also, As suggested by @StuartLC store problems and answers in a dictionary. This helps to reduce the massive number of "CASE" statements.

I did a sample implementation to demonstrate my my suggestions

Interfaces

    public interface IProblemAnswer
    {
        public int Id { get;}
        public string Name { get; }
        public string Answer();
    }

Class implementation

Class that can be instantiated from win form.. In order get a list of problems and get answer to selected problem.

public class ProjectEulerProblemProvider
{
    private Dictionary<int, IProblemAnswer> _problemAnswers;

    public ProjectEulerProblemProvider()
    {
        _problemAnswers = new Dictionary<int, IProblemAnswer>();
        var problemOne = new ProblemOne();
        _problemAnswers.Add(problemOne.Id, problemOne);
        var problemTwo = new ProblemTwo();
        _problemAnswers.Add(problemTwo.Id, problemTwo);
    }

    public List<string> GetProblemNumberList()
    {
        return _problemAnswers.Select(c=>c.Key.ToString()).ToList();
    }

    public string GetAnswer(int problem)
    {
        IProblemAnswer answer;
        _problemAnswers.TryGetValue(problem,out answer);
        if (answer != null)
        {
            return answer.Answer();
        }
        else
        {
            return string.Empty;
        }
    }

}

Individual IProblemAnswer implementation per problem-answer. adhering to single responsibility

public class ProblemOne : IProblemAnswer
{

    private readonly int _id = 1;
    private readonly string _name = "Problem 1";


    public int Id
    {
        get
        {
            return _id;
        }
    }

    public string Name
    {
        get
        {
            return _name;
        }
    }

    public string Answer()
    {
        return "answer to problem 1";
    }
}

public class ProblemTwo : IProblemAnswer
{

    private readonly int _id = 2;
    private readonly string _name = "Problem 2";


    public int Id
    {
        get
        {
            return _id;
        }
    }

    public string Name
    {
        get
        {
            return _name;
        }
    }

    public string Answer()
    {
        return "answer to problem 2";
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.