I've programmed a few Project Euler problem but they are all separated in different solutions so I decided to group them all together in a nice Windows forms application. Here's how it looks like :
You can select any of the available problems from the list check the answer and the execution time. I'm looking primarly for code style review or maybe a better way of separating classes and the overall architecture.
Main form class
public partial class Form1 : Form
{
private readonly Dictionary<string, IProblem> problems = new Dictionary<string, IProblem>();
private readonly Dictionary<int, ProblemOutput> solvedProblems = new Dictionary<int, ProblemOutput>();
private IProblem currentProblem;
public Form1()
{
InitializeComponent();
LoadProblems();
}
private void LoadProblems()
{
Type[] allProblems = GetDerivedTypesFor(typeof(IProblem)).ToArray();
foreach (Type problem in allProblems)
{
ProblemSelector.Items.Add(FormatName(problem.Name));
IProblem problemInstance = CreateInstanceByName<IProblem>(problem.Name);
problems.Add(problem.Name, problemInstance);
}
}
private string FormatName(string inputString)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < inputString.Length; i++)
{
if (result.Length > 0)
{
if (char.IsUpper(inputString[i]))
{
result.Append(' ');
}
else if (i - 1 >= 0 && char.IsNumber(inputString[i]) && char.IsLetter(inputString[i - 1]))
{
result.Append(' ');
}
}
result.Append(inputString[i]);
}
return result.ToString();
}
private static T CreateInstanceByName<T>(string className)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type type = assembly.GetTypes().First(t => t.Name == className);
return (T)Activator.CreateInstance(type);
}
private static IEnumerable<Type> GetDerivedTypesFor(Type baseType)
{
Assembly assembly = Assembly.GetExecutingAssembly();
return assembly.GetTypes().Where(baseType.IsAssignableFrom).Where(t => baseType != t);
}
private void button1_Click(object sender, EventArgs e)
{
if (currentProblem != null)
{
ProblemOutput problemOutput;
if (!solvedProblems.ContainsKey(currentProblem.ID))
{
problemOutput = currentProblem.Solve();
solvedProblems.Add(currentProblem.ID, problemOutput);
}
else
{
problemOutput = solvedProblems[currentProblem.ID];
}
ProblemAnswer.Text = problemOutput.OutputText.ToString();
ProblemExecutionTime.Text = problemOutput.ExecutionTime/1000d + @" seconds";
}
}
private void ProblemSelector_SelectedIndexChanged(object sender, EventArgs e)
{
IProblem selectedProblem = problems[Regex.Replace(((Control)sender).Text, @"\s+", "")];
if (currentProblem == null || selectedProblem.ID != currentProblem.ID)
{
ProblemExecutionTime.Text = string.Empty;
ProblemAnswer.Text = string.Empty;
currentProblem = selectedProblem;
ProblemText.Text = currentProblem.Condition;
}
}
}
Base interface for all problems
public interface IProblem
{
int ID { get; }
string Condition { get; }
ProblemOutput Solve();
}
Where ProblemOutput
is primarly a wrapper/helper class
public sealed class ProblemOutput
{
private readonly double executionTime;
public double ExecutionTime => executionTime;
private readonly object outputText;
public object OutputText => outputText;
public ProblemOutput(double executionTime, object outputText)
{
this.executionTime = executionTime;
this.outputText = outputText;
}
}
Here's and example of problem class
public sealed class Problem1 : IProblem
{
public int ID => 1;
public string Condition => ProblemsConditions.ProblemConditions[ID];
public ProblemOutput Solve()
{
int sum = 0;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 1; i < 1000; i++)
{
if (i % 3 == 0 || i % 5 == 0)
{
sum += i;
}
}
sw.Stop();
return new ProblemOutput(sw.ElapsedMilliseconds, sum);
}
}
Any methods shared between at least 2 problems are grouped in the ProblemHelper
class
public static class ProblemHelper
{
private static int GetFactorial(int n)
{
int factorial = 1;
for (int i = 2; i <= n; i++)
{
factorial *= i;
}
return factorial;
}
// some other functions
}