Project architecture and naive solver solution
Set up project architecture interfaces and add a naive implementation of a solver in DumbBruteSolver
This commit is contained in:
parent
562742206a
commit
8386ace107
@ -5,7 +5,7 @@
|
|||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleHome" value="" />
|
<option name="gradleJvm" value="azul-17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
106
src/main/java/com/cleverthis/interview/DumbBruteSolver.java
Normal file
106
src/main/java/com/cleverthis/interview/DumbBruteSolver.java
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package com.cleverthis.interview;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
47
src/main/java/com/cleverthis/interview/PadlockAdapter.java
Normal file
47
src/main/java/com/cleverthis/interview/PadlockAdapter.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package com.cleverthis.interview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This defines the interface that padlocks must conform to.
|
||||||
|
* Concrete implementations will be adapted to this interface contract through concrete adapter classes.
|
||||||
|
*/
|
||||||
|
public interface PadlockAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of the padlock's physical number pad
|
||||||
|
*
|
||||||
|
* @return A count of the physical buttons on the padlock
|
||||||
|
*/
|
||||||
|
int getNumpadSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write key presses to the input buffer of the padlock
|
||||||
|
* @param address The position / index of the button that is pressed. For example, address 1 is the first button pressed.
|
||||||
|
* @param keyIndex The value of the button that is pressed. Cannot be greater than the numpad size, as the buttons increment
|
||||||
|
* sequentially
|
||||||
|
* @return The old value of keyIndex at the inputted address
|
||||||
|
*/
|
||||||
|
Integer writeInputBuffer(int address, int keyIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the inputted password is correct
|
||||||
|
* @return True if password is correct, false otherwise
|
||||||
|
*/
|
||||||
|
boolean isPasscodeCorrect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the write counter
|
||||||
|
* @return The number of times a write operation has occurred
|
||||||
|
*/
|
||||||
|
long getWriteCounter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the check counter
|
||||||
|
* @return The number of times the password has been checked for correctness
|
||||||
|
*/
|
||||||
|
long getCheckCounter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets both the check and write counters
|
||||||
|
*/
|
||||||
|
void resetCounter();
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.cleverthis.interview;
|
||||||
|
|
||||||
|
import com.cleverthis.interview.padlock.PadlockImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The concrete implementation of PadlockAdapter that communicates with the padlock directly through a Java
|
||||||
|
* class
|
||||||
|
*/
|
||||||
|
public class PadlockJavaAdapter extends PadlockImpl implements PadlockAdapter {
|
||||||
|
/**
|
||||||
|
* Create a padlock instance.
|
||||||
|
*
|
||||||
|
* @param numpadSize The number of buttons on the numpad of this lock.
|
||||||
|
*/
|
||||||
|
public PadlockJavaAdapter(int numpadSize) {
|
||||||
|
super(numpadSize);
|
||||||
|
|
||||||
|
for(int i = 0; i < this.getNumpadSize(); i++) {
|
||||||
|
this.writeInputBuffer(i, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package com.cleverthis.interview;
|
|
||||||
|
|
||||||
import com.cleverthis.interview.padlock.PadlockImpl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a placeholder class showing a simple boilerplate.
|
|
||||||
* This class is not required, so you can replace with your own architecture.
|
|
||||||
*/
|
|
||||||
public class Solution {
|
|
||||||
public void solve(PadlockImpl padlock) {
|
|
||||||
throw new RuntimeException("TODO");
|
|
||||||
}
|
|
||||||
}
|
|
13
src/main/java/com/cleverthis/interview/SolverInterface.java
Normal file
13
src/main/java/com/cleverthis/interview/SolverInterface.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.cleverthis.interview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that defines the class signature for solver implementations
|
||||||
|
*/
|
||||||
|
public interface SolverInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solves the padlock passed in that conforms to the PadlockAdapter interface
|
||||||
|
* @param padlockAdapter the padlock object to solve
|
||||||
|
*/
|
||||||
|
void solve(PadlockAdapter padlockAdapter);
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.cleverthis.interview;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for DumbBruteSolver class
|
||||||
|
*
|
||||||
|
* Tests that lexicographically ordered permutations are properly calculated, and that the solver works
|
||||||
|
*/
|
||||||
|
public class DumbBruteSolverTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a 7-button padlock can be solved
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
protected void testSolver() {
|
||||||
|
DumbBruteSolver dumbSolver = new DumbBruteSolver();
|
||||||
|
PadlockJavaAdapter padlock = new PadlockJavaAdapter(7);
|
||||||
|
|
||||||
|
dumbSolver.solve(padlock);
|
||||||
|
|
||||||
|
assertTrue(padlock.isPasscodeCorrect());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the next permutation is calculated correctly
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
protected void testOnePermutation() {
|
||||||
|
DumbBruteSolver dumbSolver = new DumbBruteSolver();
|
||||||
|
|
||||||
|
Integer[] permutation = new Integer[] {1, 2, 3, 4};
|
||||||
|
Integer[] correctPermutation = new Integer[] {1, 2, 4, 3};
|
||||||
|
|
||||||
|
dumbSolver.calculateNextPermutation(permutation, 4);
|
||||||
|
|
||||||
|
assertArrayEquals(permutation, correctPermutation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether two consecutive permutations are correct
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
protected void testTwoPermutations() {
|
||||||
|
DumbBruteSolver dumbSolver = new DumbBruteSolver();
|
||||||
|
|
||||||
|
Integer[] permutation = new Integer[] {1, 2, 3, 4};
|
||||||
|
Integer[] correctPermutation = new Integer[] {1, 3, 2, 4};
|
||||||
|
|
||||||
|
for(int i = 0; i < 2; i++) {
|
||||||
|
dumbSolver.calculateNextPermutation(permutation, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals(permutation, correctPermutation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether 23 consecutive permutations are correct
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
protected void test23Permutations() {
|
||||||
|
DumbBruteSolver dumbSolver = new DumbBruteSolver();
|
||||||
|
|
||||||
|
Integer[] permutation = new Integer[] {1, 2, 3, 4};
|
||||||
|
Integer[] correctPermutation = new Integer[] {4, 3, 2, 1};
|
||||||
|
|
||||||
|
for(int i = 0; i < 23; i++) {
|
||||||
|
dumbSolver.calculateNextPermutation(permutation, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals(permutation, correctPermutation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the 24th permutation returns false, signifying we've exhausted all possible permutations
|
||||||
|
* for a list of size 4
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
protected void test24Permutations() {
|
||||||
|
DumbBruteSolver dumbSolver = new DumbBruteSolver();
|
||||||
|
|
||||||
|
Integer[] permutation = new Integer[] {1, 2, 3, 4};
|
||||||
|
|
||||||
|
for(int i = 0; i < 23; i++) {
|
||||||
|
assertTrue(dumbSolver.calculateNextPermutation(permutation, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(dumbSolver.calculateNextPermutation(permutation, 4));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@ import com.cleverthis.interview.padlock.PadlockImpl;
|
|||||||
* Performance test but not mean to run in unit test.
|
* Performance test but not mean to run in unit test.
|
||||||
*/
|
*/
|
||||||
public class PerformanceAnalyze {
|
public class PerformanceAnalyze {
|
||||||
private static void solve(PadlockImpl padlock) {
|
private static void solve(PadlockAdapter padlock) {
|
||||||
new Solution().solve(padlock);
|
new DumbBruteSolver().solve(padlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int TOTAL_RUN = 500;
|
private static final int TOTAL_RUN = 500;
|
||||||
@ -22,10 +22,10 @@ public class PerformanceAnalyze {
|
|||||||
long timeSum = 0;
|
long timeSum = 0;
|
||||||
long writeSum = 0;
|
long writeSum = 0;
|
||||||
for (int i = 0; i < TOTAL_RUN; i++) {
|
for (int i = 0; i < TOTAL_RUN; i++) {
|
||||||
PadlockImpl padlock = new PadlockImpl(NUMPAD_SIZE);
|
PadlockAdapter padlock = new PadlockJavaAdapter(NUMPAD_SIZE);
|
||||||
padlock.resetCounter();
|
padlock.resetCounter();
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
solve(padlock);
|
solve((PadlockAdapter) padlock);
|
||||||
long end = System.currentTimeMillis();
|
long end = System.currentTimeMillis();
|
||||||
if (!padlock.isPasscodeCorrect()) throw new IllegalStateException(
|
if (!padlock.isPasscodeCorrect()) throw new IllegalStateException(
|
||||||
"Invalid solution: passcode not correct after return");
|
"Invalid solution: passcode not correct after return");
|
||||||
|
@ -7,7 +7,7 @@ import com.cleverthis.interview.padlock.PadlockImpl;
|
|||||||
*/
|
*/
|
||||||
class SolutionTest extends SolutionTestBase {
|
class SolutionTest extends SolutionTestBase {
|
||||||
@Override
|
@Override
|
||||||
protected void solve(PadlockImpl padlock) {
|
protected void solve(PadlockAdapter padlock) {
|
||||||
new Solution().solve(padlock);
|
new DumbBruteSolver().solve(padlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,10 +13,10 @@ public abstract class SolutionTestBase {
|
|||||||
/**
|
/**
|
||||||
* Implement your solution in this function.
|
* Implement your solution in this function.
|
||||||
* */
|
* */
|
||||||
protected abstract void solve(PadlockImpl padlock);
|
protected abstract void solve(PadlockAdapter padlock);
|
||||||
|
|
||||||
protected void verify(int numpadSize) {
|
protected void verify(int numpadSize) {
|
||||||
PadlockImpl padlock = new PadlockImpl(numpadSize);
|
PadlockAdapter padlock = new PadlockJavaAdapter(numpadSize);
|
||||||
solve(padlock);
|
solve(padlock);
|
||||||
assertTrue(padlock.isPasscodeCorrect());
|
assertTrue(padlock.isPasscodeCorrect());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user