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; } }