Thursday, July 7, 2016

Locker code with 5-digit key

Question: You are given a locker that takes 5 digits as input. It always uses the last 5 digits as input. For example if 23456 have been entered, it checks if that is correct or else return failure. Now if a number 7 is entered, it reuses the last 4 of the previous number and checks for 34567 to see if that can open the key. The goal of the problem is to enter the least number of keys and open the locker.

Answer: Since we want to try to get to the key with the least number of keystrokes, we need to try to re-use as many numbers as possible from the last entry. So the approach here is to try to find an un-entered number by using as many digits as possible from the last key. In the below example, the key is hard coded as 98345.

class LockerKey
{
    private static string key = "98345";
    private static string sequence;
    private static int numKeysEntered;
    private static int numComparisonsMade;
    private static Dictionary<string, bool> elapsedSet = new Dictionary<string, bool>();

    /// <summary>
    /// This method gets called from an external source to solve for the key
    /// Start with 00000 as the key and then try to find the key
    /// </summary>
    /// <returns>the key</returns>
    public static string Solve()
    {
        sequence = "00000";
        elapsedSet.Add(sequence, true);

        // Using this as an exit condition
        while (numComparisonsMade < 100000)
        {
            for (int i = 1; i <= 4; i++)
            {
                var newKeys = GetKeyOfLength(i, 5);
                if (newKeys != null)
                {
                    var isMatchFound = EnterKeys(newKeys);

                    if (isMatchFound)
                    {
                        var start = sequence.Length - 5;
                        var finalKey = sequence.Substring(start, 5);
                        Console.WriteLine("Match Found: {0}, NumKeyPresses: {1}, NumComparisonsMade: {2}", finalKey, numKeysEntered, numComparisonsMade);
                        return finalKey;
                    }
                    break;
                }
            }
        }
        return null;
    }

    /// <summary>
    /// Checks whether the entered value is the key
    /// </summary>
    /// <returns>true if the last 5 digits make up the key</returns>
    private static bool IsKey()
    {
        numComparisonsMade++;
        var start = sequence.Length - 5; 
        var newKey = sequence.Substring(start, 5);
        elapsedSet.Add(newKey, true);
        if (newKey == key)
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// Enters keys as represented by the string input
    /// </summary>
    /// <param name="newKeys"></param>
    /// <returns></returns>
    private static bool EnterKeys(string newKeys)
    {
        foreach (char c in newKeys.ToCharArray())
        {
            sequence += c.ToString();
            numKeysEntered++;
        }

        var rtnValue = IsKey();
        return rtnValue;
    }


    private static string GetKeyOfLength(int  length, int maxLength)
    {
        Console.WriteLine("GetKeyOfLength called with {0} and {1}", length, maxLength);
        string appendedString = null;
        var maxValue = Math.Pow(10, length);
        var reuseLength = maxLength - length;
        var reuseStart = sequence.Length - reuseLength;
        var reuseString = sequence.Substring (reuseStart, reuseLength);
        for (int i = 0; i < maxValue; i++)
        {
            appendedString = i.ToString().PadLeft(length, '0');
            var newKey = reuseString + appendedString;
            if (!elapsedSet.Keys.Contains(newKey))
            {
                Console.WriteLine("AppendedString: {0} New Key: {1} i: {2} maxValue: {3}", appendedString, newKey, i, maxValue);
                break;
            }
            else
            {
                appendedString = null;
            }
        }
        return appendedString;
    }
}