105 lines
4.4 KiB
Java
105 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
|
|
*
|
|
* 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, numpadSize);
|
|
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
|
|
* @param numpadSize The number of items in the set to be permuted
|
|
* @return true if next permutation successfully generated, false if permutations have been exhausted
|
|
*/
|
|
protected boolean calculateNextPermutation(Integer[] currentPermutation, int numpadSize) {
|
|
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;
|
|
}
|
|
}
|