padlock-solver/src/main/java/com/cleverthis/interview/WriteAwareBruteSolver.java

87 lines
3.5 KiB
Java

package com.cleverthis.interview;
import java.util.TreeSet;
/**
* A write-aware brute solver implementation that uses the levenshtein distance to sort passcode permutations
* into a tree. Walking the tree in-order (Likely a depth-first traversal internally) results in a naive nearest-neighbor
* optimization that generally performs well, but may perform poorly in some cases. Heuristic solutions exist that have
* acceptable performance, and this is a more naive heuristic implementation.
*
* This permutation optimization problem is NP-hard and can be represented as the traveling salesperson problem.
* Representing passcode permutations as vertices and the levenshtein distance as edges in an undirected, weighted graph,
* an optimal solution is the shortest path satisfying a tour of the entire graph.
*
* A more advanced solution (based on Christofides, or another TSP heuristic) may result in more performance gains, but
* the performance of this nearest-neighbor solution is adequate for the keypad sizes under test. A keypad size of 9
* results in a graph of vertex-count 9!. It's unlikely performance gains from a more intelligent TSP solver would
* offset the performance cost of building a graph and solving for an optimal tour.
*/
public class WriteAwareBruteSolver extends DumbBruteSolver {
private final TreeSet<IntegerLevenshtein> orderedTree;
private Integer numpadSize;
/**
* Creates an ordered tree on instantiation to be used as a cache for subsequent brute-force solves.
* @param numpadSize The size of the padlock's numpad, used in generation of the internal ordered tree
*/
public WriteAwareBruteSolver(int numpadSize) {
orderedTree = new TreeSet<IntegerLevenshtein>();
this.numpadSize = numpadSize;
this.createOrderedTree(numpadSize);
}
/**
* Solves the padlock passed in to the method. The padlock's internal state should be correct after this method
* runs.
* @param padlockAdapter A padlock conforming to the PadlockAdapter contract
*/
@Override
public void solve(PadlockAdapter padlockAdapter) {
int padlockNumpadSize = padlockAdapter.getNumpadSize();
if (this.numpadSize != padlockNumpadSize) {
this.createOrderedTree(padlockNumpadSize);
}
for (IntegerLevenshtein integerLevenshtein : orderedTree) {
if (this.checkPermutation(integerLevenshtein.getIntegerData(), padlockAdapter)) {
return;
}
}
}
/**
* Returns the size of the tree (the number of possible permutations)
* @return the size of the ordered tree
*/
public Integer getTreeSize() {
return this.orderedTree.size();
}
/**
* Creates an ordered tree of possible passcode permutations
* @param size The number of keys on the numpad
*/
protected void createOrderedTree(int size) {
this.numpadSize = size;
orderedTree.clear();
Integer[] currentPermutation = new Integer[numpadSize];
for(int i = 0; i < numpadSize; i++) {
currentPermutation[i] = i;
}
boolean morePermutationsExist = true;
do {
IntegerLevenshtein levenshteinPermutation = new IntegerLevenshtein(numpadSize);
levenshteinPermutation.setIntegerData(currentPermutation);
orderedTree.add(levenshteinPermutation);
morePermutationsExist = this.calculateNextPermutation(currentPermutation);
} while(morePermutationsExist);
}
}