|
| 1 | +--- |
| 2 | + |
| 3 | +title: Google FooBar First Challenge |
| 4 | +subtitle: Solving Caesar Cipher |
| 5 | +date: '2020-07-19' |
| 6 | +categories: ['google', 'challenge', 'series', 'cipher'] |
| 7 | +keywords: ['google', 'challenge', 'series', 'cipher', 'first', 'caesar'] |
| 8 | +slug: google-foobar-first-challenge-caesar-cipher |
| 9 | +cover: './img/google-foobar-first-challenge-solved.png' |
| 10 | +type: 'BlogPost' |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | + |
| 15 | +### Background |
| 16 | +In my quest of searching for the next opportunity and in the process of preparing technical interviews, I received a surprise code challenge invitation from Google on July 26, 2020. After accepting the invitation, I was redirected to https://foobar.withgoogle.com. |
| 17 | + |
| 18 | +I am going to record my experience with the code challenge experience. |
| 19 | + |
| 20 | + |
| 21 | +### The Problem: Caesar Cipher Decryption |
| 22 | +Decrypt a code where every lowercase letter [a..z] is replaced with the corresponding one in [z..a], while every other character (including uppercase letters and punctuation) is left untouched. That is, 'a' becomes 'z', 'b' becomes 'y', 'c' becomes 'x', etc. For instance, the word "vmxibkgrlm", when decoded, would become "encryption". |
| 23 | + |
| 24 | +There was some limitation to execution time and the external Java library usage environment provided was default Java Runtime Environment (JRE) 8. |
| 25 | + |
| 26 | + |
| 27 | +### What is Caesar Cipher? |
| 28 | +A `Cipher` is a method for encrypting a message, intending to make it less readable. As for the `Caesar Cipher`, it's a substitution cipher that transforms a message by shifting its letters by a given offset. |
| 29 | + |
| 30 | + |
| 31 | +### Initial Thoughts |
| 32 | +After going through the problem and some analysis, the rough idea I could come up with as a part of the solution was manipulating the ASCII value of the letters. Addition and subtraction of numeric value of numbers should do the trick but at this point I did not think of `Caesar Cipher` at all. Implementing the initial idea to anything concrete was taking some time so I went with the simplest solution that I could work out. |
| 33 | + |
| 34 | + |
| 35 | +### First Attempt |
| 36 | +To solve the problem I needed a data structure to keep a map of the encryption and easily access the decrypted value. `HashMap` was the best fit. The other part of the solution required a data structure to store the decrypted characters as a `String`. Although `Character Array` would have been the ideal way, `StringBuilder` with `Java 8` `SteamAPI` was the quickest option that did the trick. Hence, I went with the implementation built based on these base data types. The rough solution did the job and ran successfully at my local but sadly it did not pass the challenge tests. |
| 37 | + |
| 38 | +```java |
| 39 | +public static String solnWithStrBuilder(String x) { |
| 40 | + StringBuilder stb = new StringBuilder(); |
| 41 | + x.chars().forEach(i -> stb.append(decrypt(i))); |
| 42 | + return stb.toString(); |
| 43 | +} |
| 44 | +public static char decrypt(int encryptedVal) { |
| 45 | + if ( encryptedVal >= 97 && encryptedVal <= 122) { |
| 46 | + char encodedChar; |
| 47 | + Map<Integer, Character> decryptKey = new HashMap<>(); |
| 48 | + for (int i = 122, j = 97; i >= 97; i--, j++) { |
| 49 | + encodedChar = (char) i; |
| 50 | + decryptKey.put(j, encodedChar); |
| 51 | + } |
| 52 | + return decryptKey.get(encryptedVal); |
| 53 | + } |
| 54 | + else return (char)encryptedVal; |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | + |
| 59 | +### Second Iteration |
| 60 | +I reflected back on the limitations. I knew a cheaper data structure, `Character Array`, could have done the job instead of `StringBuilder` class. I thought that might be the reason that the solution failed to fulfill the criteria, so I refactored the code replacing StringBuilder with a simple Character Array. But this solution also failed to pass any of the tests. |
| 61 | + |
| 62 | +At this point I was getting a little bit frustrated because the errors were not thrown back properly. I was not sure what failed. To be honest, I had lost interest after one hour of trials and gave up. I needed to prepare for interviews and I went back to my previous way, learning basics and solving challenges from different sources. |
| 63 | + |
| 64 | +```java |
| 65 | +public static String solnWithArray(String encryptedText) { |
| 66 | + char[] encryptedCharArr = new char[encryptedText.length()]; |
| 67 | + Map<Integer, Character> decryptKey = new HashMap<>(); |
| 68 | + char encodedChar; |
| 69 | + |
| 70 | + for (int i = 122, j = 97; i >= 97; i--, j++) { |
| 71 | + encodedChar = (char) i; |
| 72 | + decryptKey.put(j, encodedChar); |
| 73 | + } |
| 74 | + |
| 75 | + for (int i = 0; i < encryptedText.length(); i++) { |
| 76 | + char encryptedVal = encryptedText.charAt(i); |
| 77 | + if ( encryptedVal >= 97 && encryptedVal <= 122) { |
| 78 | + encryptedCharArr[i] = decryptKey.get((int)encryptedVal); |
| 79 | + |
| 80 | + } else encryptedCharArr[i] = encryptedVal; |
| 81 | + } |
| 82 | + |
| 83 | + return new String(encryptedCharArr); |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | + |
| 88 | +### Third Iteration |
| 89 | +I wanted to solve the problem and came back to it again with less than 3 hours left to complete the other day. |
| 90 | + |
| 91 | +As I had never participated in `Google FooBar Challenge`, and even never heard of it, I wanted to know more as I did not want to waste more time if there would be no value in participating in the event, and I needed to continue my preparation. So after some search, I knew more about the challenge and that it was an invitation only and used by Google for recruitment. It added more motivation to solve the problem but then I was running out of time. |
| 92 | + |
| 93 | +I reflected back on the problem statement and the constraints again. As I knew that there would be other ASCII based optimum solutions which might satisfy the conditions, I did some more research on ways to encrypt letters with other letters and other standard encryption processes. It led to the `Cesar Cipher` which was the term that I was looking for. After digging more into it, I found a simple example by [Baeldung.com](https://www.baeldung.com/java-caesar-cipher) which laid the foundation to my initial ASCII based solution. |
| 94 | + |
| 95 | +```java |
| 96 | +StringBuilder result = new StringBuilder(); |
| 97 | +for (char character : message.toCharArray()) { |
| 98 | + if (character != ' ') { |
| 99 | + int originalAlphabetPosition = character - 'a'; |
| 100 | + int newAlphabetPosition = (originalAlphabetPosition + offset) % 26; |
| 101 | + char newCharacter = (char) ('a' + newAlphabetPosition); |
| 102 | + result.append(newCharacter); |
| 103 | + } else { |
| 104 | + result.append(character); |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +return result; |
| 109 | +``` |
| 110 | + |
| 111 | +Baeldung's example `encrypts` letters. It did not `decrypt`. Letter `a` was used as the base for the encryption process. Understanding the encryption process was very important to successfully decrypt the cipher. So based on this framework I created an encryption system that generates the cipher which is our problem. |
| 112 | + |
| 113 | +```java |
| 114 | +public static String encrypt(String textToEncrypt) { |
| 115 | + StringBuilder result = new StringBuilder(); |
| 116 | + |
| 117 | + for (char character : textToEncrypt.toCharArray()) { |
| 118 | + |
| 119 | + if ( (int)character >= 97 && (int)character <= 122 ) { |
| 120 | + int originalAlphabetPosition = character - 'z'; |
| 121 | + int newAlphabetPosition = ( originalAlphabetPosition + 25 ) % 26; |
| 122 | + result.append((char) ('z' - newAlphabetPosition)); |
| 123 | + |
| 124 | + } else { |
| 125 | + result.append(character); |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + return new String(result); |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +Then creating a decryption mechanism was some maths and common sense. Then again, the solution failed. |
| 134 | + |
| 135 | +```java |
| 136 | +public static String solutionWithOffset(String encryptedText) { |
| 137 | + StringBuilder result = new StringBuilder(); |
| 138 | + |
| 139 | + for (char character : encryptedText.toCharArray()) { |
| 140 | + |
| 141 | + if ( (int)character >= 97 && (int)character <= 122 ) { |
| 142 | + int originalAlphabetPosition = character - 'z'; |
| 143 | + int newAlphabetPosition = (originalAlphabetPosition + 25 ) % 26; |
| 144 | + result.append((char) ('z' + newAlphabetPosition)); |
| 145 | + |
| 146 | + } else { |
| 147 | + result.append(character); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + return new String(result); |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | + |
| 156 | +### Accepted Solution |
| 157 | +The default example had implemented StringBuilder class which I thought was the reason for the failed tests. I replaced StringBuilder with Array implementation and the solution passed all the tests. Yay! |
| 158 | + |
| 159 | +```java |
| 160 | +public static String solution(String x) { |
| 161 | + int strLength = x.length(); |
| 162 | + char[] encryptedCharArr = new char[strLength]; |
| 163 | + |
| 164 | + for (int i = 0; i < strLength; i++) { |
| 165 | + int character = x.charAt(i); |
| 166 | + |
| 167 | + if ( character >= 97 && character <= 122 ) { |
| 168 | + int originalAlphabetPosition = character - 'z'; |
| 169 | + int newAlphabetPosition = (originalAlphabetPosition + 25 ) % 26; |
| 170 | + encryptedCharArr[i] = (char) ('z' + newAlphabetPosition); |
| 171 | + |
| 172 | + } else { |
| 173 | + encryptedCharArr[i] = (char)character; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + return new String(encryptedCharArr); |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | + |
| 182 | +### Further Refactoring, Test Harness |
| 183 | +After submitting the solution I reviewed the solution and the recalled overall process. Being a firm believer of `Test Driven Development (TDD)`, I wanted safety to keep the behaviours of the solution the same, I wrote simple `Unit Tests`. The unit tests did not need to be extensive covering all edge cases; neither should have many use cases covered as it had already passed the test cases of Google. I just needed something to preserve what was functional in my local. |
| 184 | + |
| 185 | +```java |
| 186 | +class CaesarCipherTest { |
| 187 | + |
| 188 | + String result = "wrw blf hvv ozhg mrtsg'h vkrhlwv?"; |
| 189 | + String expected = "did you see last night's episode?"; |
| 190 | + |
| 191 | + @Test |
| 192 | + void solution() { |
| 193 | + String actual = CaesarCipher.solution(result); |
| 194 | + Assertions.assertEquals(expected, actual); |
| 195 | + } |
| 196 | + |
| 197 | + @Test |
| 198 | + void submittedSolution() { |
| 199 | + String actual = CaesarCipher.submittedSolution(result); |
| 200 | + Assertions.assertEquals(expected, actual); |
| 201 | + } |
| 202 | + |
| 203 | + @Test |
| 204 | + void encrypt() { |
| 205 | + String expected = "wrw blf hvv ozhg mrtsg'h vkrhlwv?"; |
| 206 | + String plainText = "did you see last night's episode?"; |
| 207 | + result = CaesarCipher.encrypt(plainText); |
| 208 | + Assertions.assertEquals(expected, result); |
| 209 | + } |
| 210 | + |
| 211 | + @Test |
| 212 | + void solnWithArray() { |
| 213 | + Assertions.assertEquals( expected, CaesarCipher.solnWithArray(result) ); |
| 214 | + } |
| 215 | + |
| 216 | + @Test |
| 217 | + void solnWithStrBuilder() { |
| 218 | + Assertions.assertEquals(expected, CaesarCipher.solnWithArray(result) ); |
| 219 | + } |
| 220 | + |
| 221 | + @Test |
| 222 | + void decrypt() { |
| 223 | + char expected = 'z'; |
| 224 | + char result= 'a'; |
| 225 | + Assertions.assertEquals( expected , CaesarCipher.decrypt(result) ); |
| 226 | + } |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +Now with the protection of the tests, I refactored the submitted solution. |
| 231 | + |
| 232 | +```java |
| 233 | +public static String solution(String cipher) { |
| 234 | + int cipherLen = cipher.length(); |
| 235 | + char[] decryptedCharArr = new char[cipherLen]; |
| 236 | + |
| 237 | + for (int i = 0; i < cipherLen; i++) { |
| 238 | + int character = cipher.charAt(i); |
| 239 | + |
| 240 | + if ( character >= 97 && character <= 122 ) |
| 241 | + decryptedCharArr[i] = (char) ( 'z' - ( ( character + 7) % 26 )); |
| 242 | + else |
| 243 | + decryptedCharArr[i] = (char)character; |
| 244 | + } |
| 245 | + |
| 246 | + return new String(decryptedCharArr); |
| 247 | +} |
| 248 | +``` |
| 249 | + |
| 250 | +### Review |
| 251 | +`Google FooBar Code Challenge` was fun. Sometimes `the unknown` leads to better results as if there had been clear test case failure outputs, It would not have pushed myself to think and rethink more. On the other hand, I would have found the solution straight forward but problems in real life are also abstract. The constraints and the test cases pushed me to use optimum data structures and algorithms. |
| 252 | + |
| 253 | +Previously, I had an interview with Google in July 2019. It was more of a casual talk I had with a nice lady who wanted to know a bit of me and to see if I fit the available role at a certain location. Unfortunately, it did not work out. |
| 254 | + |
| 255 | +I am grateful to both opportunities but I am enjoying this experience more that may be because I'm more of a `code person`. |
| 256 | + |
| 257 | +Thank you Google! |
| 258 | + |
| 259 | + |
| 260 | +If you code is a better language to you then please find more at [Github](https://github.com/JavaCheatsheet/codechallenge). |
| 261 | + |
0 commit comments