Implement the padlock

This commit is contained in:
Rui Hu
2024-03-12 15:18:46 +08:00
parent 4953d93d5e
commit 6c798c04bd
15 changed files with 700 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
package com.cleverthis.interview.padlock;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.cleverthis.interview.padlock.Utils.ensureSleep;
/**
* This is a logical representation of a physical padlock with a numpad.
* <br/>
* The padlock has the following features:
* <ul>
* <li>All the keys/buttons on the numpad is used exactly once.
* Which means codes like 1234 and 1432 is valid, but codes like
* 112 or 1332 is invalid.</li>
* <li>Writing to the passcode input buffer of the padlock is an
* expensive operation, since the hardware need to figure out the
* memory address every time you write. Thus an write operation
* can take seconds to finish.</li>
* <li>Ask padlock if a passcode is correct is fast.</li>
* <li>If the padlock reject your input, the input buffer remain
* unchanged. There is NO reset on failed attempts.</li>
* </ul>
* <br/>
* After create, the input buffer is empty, you have to initialize.
*/
public class Padlock {
private final int numpadSize;
private final Integer[] inputBuffer;
private final Integer[] correctPasscode;
/**
* Create a padlock instance.
*
* @param numpadSize The number of buttons on the numpad of this lock.
*/
public Padlock(int numpadSize) {
if (numpadSize < 1) throw new IllegalArgumentException("numpadSize must be a positive number");
this.numpadSize = numpadSize;
this.inputBuffer = new Integer[numpadSize];
List<Integer> answer = new ArrayList<>(numpadSize);
for (int i = 0; i < numpadSize; i++)
answer.add(i);
Collections.shuffle(answer);
this.correctPasscode = answer.toArray(new Integer[0]);
}
public int getNumpadSize() {
return numpadSize;
}
/**
* Write a digit into padlock's input buffer. This is a very expensive operation.
*
* @param address The digits you want to write. Range: [0, numpadSize)
* @param keyIndex The key/button index you want to put here. Range: [0, numpadSize)
* @return The old value, null if not initialized.
*/
public synchronized Integer writeInputBuffer(int address, int keyIndex) {
ensureSleep(1000);
if (keyIndex < 0 || keyIndex >= numpadSize)
throw new IllegalArgumentException(
"keyIndex out of range. Keypad size: " + numpadSize + ", keyIndex: " + keyIndex);
Integer oldValue = inputBuffer[address];
inputBuffer[address] = keyIndex;
return oldValue;
}
/**
* Check if the input buffer contains a correct passcode.
*
* @return true if the passcode is correct; false if passcode is wrong.
* @throws IllegalStateException if the input buffer is not a valid passcode
*/
public synchronized boolean isPasscodeCorrect() {
// first check if the input is legal
boolean[] uniqueTestArr = new boolean[numpadSize];
for (Integer i : inputBuffer) {
if (i == null) throw new IllegalStateException(
"Passcode invalid: contain uninitialized value. " + Arrays.toString(inputBuffer));
if (uniqueTestArr[i]) throw new IllegalStateException(
"Passcode invalid: contain duplicated value. " + Arrays.toString(inputBuffer));
uniqueTestArr[i] = true;
}
// if no exception, means:
// every digit is unique, and every digit is initialized
// aka this is a valid code
// now compare with our answer
return Arrays.equals(correctPasscode, inputBuffer);
}
}

View File

@@ -0,0 +1,22 @@
package com.cleverthis.interview.padlock;
class Utils {
/**
* Ensure we will wait a given amount of time even if there are interruptions.
*
* @param millis The time you want to sleep, measure in millisecond.
*/
public static void ensureSleep(long millis) {
long endTime = System.currentTimeMillis() + millis;
while (endTime > System.currentTimeMillis()) {
try {
//noinspection BusyWait
Thread.sleep(endTime - System.currentTimeMillis());
} catch (InterruptedException e) {
// do nothing when interrupted, will re-sleep in next loop
} catch (IllegalArgumentException e) {
break;
}
}
}
}