I've found that it's a huge pain to have to write the boilerplate code for each Project Euler problem I write (timing code, main method, what about running every problem instead of just one?). So I decided to create a program to run the problems for me.
Main.java
package projecteuler;
import projecteuler.autorun.Executor;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
Executor executor = new Executor();
executor.addAll(IntStream.range(1, 500).boxed()::iterator);
executor.run();
}
}
Problem.java
package projecteuler.autorun;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Callable;
public class Problem implements Runnable {
private final String name;
private final Callable<String> problem;
private String result;
private double time;
private Problem(String name, Callable<String> problem) {
this.name = name;
this.problem = problem;
}
/**
* Runs the problem, timing it and storing the result. This must
* be called before the getters on <code>result</code> and <code>time</code>
*/
@Override
public void run() {
try {
long start = System.nanoTime();
result = problem.call();
time = (System.nanoTime() - start) * 1e-9;
} catch (Exception e) {
e.printStackTrace(); // Swallow; we want to continue executing problems even if this problem failed.
// I think that better would be to use a logger to log
// this, but I'm unsure how to go about doing so.
}
}
/**
* Attempts to create a problem for the given number. If no suitable problem exists, throws an Exception.
* <p>
* The given Problem must exist under the package <code>projecteuler.problems</code> under the name
* <code>ProblemNUM</code>, where <code>NUM</code> is the problem's number, prefixed by zeros if the
* number is less than 3 digits long. The problem must only have a <code>problem()</code> method, it
* need not be public. It's return value is the assumed result of the function. Any return value is
* valid, as long as it can be converted to a String via <code>java.util.Objects.toString</code>
*
* @param num
* @return A Problem for the given number.
* @throws ClassNotFoundException If the given class doesn't exist.
* @throws IllegalArgumentException If the given problem doesn't support a problem method.
*/
public static Problem number(int num) throws ClassNotFoundException {
Class problem = Class.forName(String.format("projecteuler.problems.Problem%03d", num));
if (!Arrays.stream(problem.getDeclaredMethods())
.map(Method::getName)
.anyMatch(s -> s.equals("problem"))) {
throw new IllegalArgumentException(String.format("Problem%03d must contain a method called `problem`", num));
}
Method method = null;
try {
method = problem.getDeclaredMethod("problem");
} catch (NoSuchMethodException e) {
throw new InternalError("Should not be able to reach this state; we already checked that the method exists," +
"but now we find that it doesn't.");
}
method.setAccessible(true);
Object toInvokeOn = null;
if (!Modifier.isStatic(method.getModifiers())) {
try {
toInvokeOn = problem.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException(
String.format("Problem%03d's `problem` method is not static, " +
"and the constructor can't be accessed", num
), e);
}
}
final Method finalMethod = method;
final Object finalToInvokeOn = toInvokeOn;
return new Problem("Problem " + num, () -> Objects.toString(finalMethod.invoke(finalToInvokeOn)));
}
public String getName() {
return this.name;
}
public String getResult() {
return this.result;
}
public double getTime() {
return this.time;
}
}
Executor.java
package projecteuler.autorun;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A class which runs the problems given to it, timing each one and printing the result
*/
public class Executor implements Runnable {
private final List<Problem> problems = new ArrayList<>();
private static final int MAX_NAME_LENGTH = "Problem 000".length();
private static final int MAX_RESULT_LENGTH = 50;
private static final String FORMAT_STRING = "%-" + MAX_NAME_LENGTH + "s %-" + MAX_RESULT_LENGTH + "s %s%n";
/**
* Adds the given problem to the list of problems to run.
* @param problem
*/
public void add(Problem problem) {
problems.add(problem);
}
/**
* Adds all the given problems to the list of problems to run.
* @param problems The problems to add.
*/
public void addAll(Problem... problems) {
for (Problem problem : problems) {
add(problem);
}
}
/**
* Adds the given problem number to the list of problems to run.
* @param num The problem number to run
* @throws ClassNotFoundException If the given problem doesn't exist.
*/
public void add(int num) throws ClassNotFoundException {
add(Problem.number(num));
}
/**
* Adds all the given problems to the list of problems to run. Any problem
* which does not exist is silently ignored.
* @param problems The problems to add.
*/
public void addAll(int... problems) {
addAll(Arrays.stream(problems).boxed()::iterator);
}
/**
* Adds all the given problems to the list of problems to run. Any problem
* which does not exist is silently ignored.
* @param problems The problems to add.
*/
public void addAll(Iterable<Integer> problems) {
for (int problemNumber : problems) {
try {
add(Problem.number(problemNumber));
} catch (ClassNotFoundException ignore) {
}
}
}
/**
* Instructs the Executor to run its set of problems.
*/
@Override
public void run() {
printHeader();
for (Problem p : problems) {
p.run();
printData(p.getName(), p.getResult(), p.getTime());
}
}
/**
* Prints a header for the entire problem set.
*/
private void printHeader() {
System.out.printf(FORMAT_STRING, "Problem", "Result", "Time");
System.out.println(
String.format("%" + MAX_NAME_LENGTH + "s#%" + MAX_RESULT_LENGTH + "s#========", "", "")
.replace(' ', '=')
.replace('#', ' ')
);
}
/**
* Prints result data for the given problem.
* @param name The name to display for the problem. Should look something like "Problem 1"
* @param result The result of running the problem.
* @param time The time it took to run the problem.
*/
private void printData(String name, String result, double time) {
System.out.printf(FORMAT_STRING, name, result, String.format("%f seconds", time));
}
}
After having these, I can now write a file like as follows, and it will automatically run (please don't review this; this is given as an example usage, not as something I want reviewed):
Problem001.java
package projecteuler.problems;
import java.util.stream.IntStream;
public class Problem001 {
int problem() {
return IntStream.range(1, 1000)
.filter(number -> number % 3 == 0 || number % 5 == 0)
.sum();
}
}
Result:
Problem Result Time
=========== ================================================== ========
Problem 1 ##HIDDEN## 0.006705 seconds