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

106 lines
4.4 KiB
Java

package com.cleverthis.interview;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream;
/**
* Brute-forces padlock using lexicographically ordered permutation generation.
*
* Permutation-generation algorithm documented at: https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order
*/
public class DumbBruteSolver implements SolverInterface {
/**
* 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 numpadSize = padlockAdapter.getNumpadSize();
Integer[] currentPermutation = new Integer[numpadSize];
for(int i = 0; i < numpadSize; i++) {
currentPermutation[i] = i;
}
while(true) {
boolean isCorrect = checkPermutation(currentPermutation, padlockAdapter);
if (!isCorrect) {
boolean nextPermutationExists = calculateNextPermutation(currentPermutation);
if(!nextPermutationExists) {
return;
}
} else {
return;
}
}
}
/**
* Writes the permutation to the padlock's memory and checks whether this permutation is the correct passcode.
* This is a naive solution that makes no considerations for write-cost.
* @param permutation The permutation to write to the padlock
* @param padlockAdapter The padlock to write to
* @return True if the correct padlock passcode has been found, false otherwise
*/
protected boolean checkPermutation(Integer[] permutation, PadlockAdapter padlockAdapter) {
for(int i = 0; i < padlockAdapter.getNumpadSize(); i++) {
padlockAdapter.writeInputBuffer(i, permutation[i]);
}
return padlockAdapter.isPasscodeCorrect();
}
/**
* Calculates the next permutation in lexicographic order, based on the algorithm linked on wikipedia
* @param currentPermutation The current permutation to run the algorithm on
* @return true if next permutation successfully generated, false if permutations have been exhausted
*/
protected boolean calculateNextPermutation(Integer[] currentPermutation) {
int numpadSize = currentPermutation.length;
if(numpadSize < 2) { return false; }
//Integer k, l;
// Find the k and l indices, such that they meet the criteria for the permutation algorithm.
// If such indice values are found, swap them, then reverse the array subset from k+1 to the end of the array
for(int k = (numpadSize - 2); k >= 0; k--) {
if(currentPermutation[k] < currentPermutation[k + 1]) {
for(int l = (numpadSize - 1); l > k; l--) {
if(currentPermutation[k] < currentPermutation[l]) {
// Swap index k value and index l value in permutations array
// TODO: Could be a better swap algorithm
int tempInt = currentPermutation[k];
currentPermutation[k] = currentPermutation[l];
currentPermutation[l] = tempInt;
// Split the currentPermutation array into two slices. The slice happens at index k, with index k
// inclusive to the first slice
Integer[] firstSlice = Arrays.stream(currentPermutation, 0, k + 1).toArray(Integer[]::new);
Integer[] secondSlice = Arrays.stream(currentPermutation, k + 1, numpadSize).toArray(Integer[]::new);
// Reverse the subset of the permutation array from index k+1 to the end of the array
Collections.reverse(Arrays.asList(secondSlice));
// Concat the non-reversed and reversed subarrays into a new permutation
Integer[] newPermutation = Stream.concat(Arrays.stream(firstSlice), Arrays.stream(secondSlice)).toArray(Integer[]::new);
// Copy the new permutation into currentPermutation to return it
System.arraycopy(newPermutation, 0, currentPermutation, 0, numpadSize);
return true;
}
}
}
}
return false;
}
}