If you want to get rid of nesting, I'd start by changing from eight variables to an array. So
private static boolean isSolution(int a, int b, int c, int d, int e, int f, int g, int h) {
if (a + b - 9 != 4)
return false;
if ((c - d) * e != 4)
return false;
if (f + g - h != 4)
return false;
if ((a + c) / f != 4)
return false;
if ((b - d) * g != 4)
return false;
if (9 - e - h != 4)
return false;
return true;
}
would become
private static boolean isSolution(int[] variables) {
if (variables[0] + variables[1] - 9 != 4) {
return false;
}
if ((variables[2] - variables[3]) * variables[4] != 4) {
return false;
}
if (variables[5] + variables[6] - variables[7] != 4) {
return false;
}
if ((variables[0] + variables[2]) / variables[5] != 4) {
return false;
}
if ((variables[1] - variables[3]) * variables[6] != 4) {
return false;
}
if (9 - variables[4] - variables[7] != 4) {
return false;
}
return true;
}
Added {}
and whitespace for ease of future reading and editing.
Or the simpler (as in the @h.j.k. answer)
private static boolean isSolution(int[] variables) {
return (variables[0] + variables[1] - 9 == 4)
&& ((variables[2] - variables[3]) * a[4] == 4)
&& (variables[5] + variables[6] - variables[7] == 4) {
&& ((variables[0] + variables[2]) / variables[5] == 4) {
&& ((variables[1] - variables[3]) * variables[6] == 4) {
&& (9 - variables[4] - variables[7] == 4);
}
And with a little algebra
private static boolean isSolution(int[] variables) {
return (variables[0] + variables[1] == 13)
&& ((variables[2] - variables[3]) * variables[4] == 4)
&& (variables[5] + variables[6] - variables[7] == 4)
&& ((variables[0] + variables[2]) / variables[5] == 4)
&& ((variables[1] - variables[3]) * variables[6] == 4)
&& (5 == variables[4] + variables[7]);
}
Why do the extra math on each iteration?
Any of these allow you to call this with an array as in
private void findAndPrintSolutions(int[] variables, int current) {
if (current >= variables.length) {
if (isSolution(variables)) {
printSolution(variables);
}
return;
}
for (int i = from; i < to; i++) {
variables[current] = i;
findAndPrintSolutions(variables, current + 1);
}
}
The printSolution
method already took an array, so this was consistent.
I also moved to
and from
into field variables as they never change.
I called this from
public void findAndPrintSolutions() {
findAndPrintSolutions(new int[SIZE], 0);
}
Note that I changed this from private
to public
so that it could be used outside this class while I left the recursive method only accessible from this class.
General purpose
This is going to be slower, as it adds additional overhead to the original iterative solution. But the real advantage of a recursive solution is adaptability. So let's keep going.
CHARGE!
We can define an interface:
public interface SolutionChecker {
public boolean isSolution(Integer[] variables);
public int getSize();
public int getTo();
public int getFrom();
}
This allows us to abstract away from any particular solution. In this example, we could implement it as something like
public class TerribleMathSolutionChecker implements SolutionChecker {
private final int SIZE = 8;
private final int TO;
private final int FROM;
public TerribleMathSolutionChecker(int from, int to) {
FROM = from;
TO = to;
}
@Override
public boolean isSolution(Integer[] variables) {
return (variables[0] + variables[1] == 13)
&& ((variables[2] - variables[3]) * variables[4] == 4)
&& (variables[5] + variables[6] - variables[7] == 4)
&& ((variables[0] + variables[2]) / variables[5] == 4)
&& ((variables[1] - variables[3]) * variables[6] == 4)
&& (5 == variables[4] + variables[7]);
}
@Override
public int getSize() {
return SIZE;
}
@Override
public int getTo() {
return TO;
}
@Override
public int getFrom() {
return FROM;
}
}
Hardcoded the size, as it needs to match isSolution
.
So we can handle terrible math today and rather swell math tomorrow. We aren't stuck writing one off code that we'll never use again. So here's our brute force solver:
public class BruteForceSolver {
private final SolutionChecker checker;
private final List<Integer[]> solutions = new ArrayList<>();
public BruteForceSolver(SolutionChecker checker) {
this.checker = checker;
}
private void solve() {
solve(new Integer[checker.getSize()], 0);
}
private void solve(Integer[] variables, int current) {
if (current >= variables.length) {
if (checker.isSolution(variables)) {
solutions.add(variables.clone());
}
return;
}
for (int i = checker.getFrom(), n = checker.getTo(); i < n; i++) {
variables[current] = i;
solve(variables, current + 1);
}
}
private static void printSolution(Integer[] solution) {
StringBuilder output = new StringBuilder();
output.append(solution[0]);
for (int i = 1; i < solution.length; i++) {
output.append(", " + solution[i]);
}
System.out.println(output.toString());
}
public void printSolutions() {
for (Integer[] solution : solutions) {
printSolution(solution);
}
}
}
which we can use with something like
public static void main(String[] args) {
BruteForceSolver problem = new BruteForceSolver(new TerribleMathSolutionChecker(1, 20));
problem.solve();
problem.printSolutions();
}
Note that I also separated output from solving here. That makes the code more reusable. I can solve without printing or print without solving again.
You may recognize the pattern used. It's essentially the same one as used with sorting using a custom Comparator
. Define an interface and implement it to allow reuse of common logic for multiple problems.