Skip to content

Commit f117862

Browse files
authored
Merge pull request #37 from codeanitdotcom/content#30-Caesar_Cipher
Content#30 caesar cipher
2 parents 1eaf022 + 71b5f5a commit f117862

File tree

1 file changed

+116
-182
lines changed

1 file changed

+116
-182
lines changed
Lines changed: 116 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
---
2-
32
title: Google FooBar First Challenge
43
subtitle: Solving Caesar Cipher
54
date: '2020-07-19'
@@ -8,254 +7,189 @@ keywords: ['google', 'challenge', 'series', 'cipher', 'first', 'caesar']
87
slug: google-foobar-first-challenge-caesar-cipher
98
cover: './img/google-foobar-first-challenge-solved.png'
109
type: 'BlogPost'
11-
1210
---
1311

14-
15-
### Background
16-
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 where I completed my first challenge.
17-
18-
In this post I will share my experience solving the challenge.
12+
In this post I will share my experience solving the first `Google FooBar Challenge`.
1913

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.
14+
While doing some search on Google, I received an invitation to participate in a code challenge by Google. I had no previous idea about it, frankly never heard of it. I accepted the invitation and I was redirected to [FooBar](https://foobar.withgoogle.com). After log in, a `CLI` with the instructions about the challenge and how to request a challenge was provided. The challenge had to be solved within 2 days.
2515

16+
### The Challenge
17+
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".
2618

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-
19+
__Constraints for the solution:__
20+
- Java 8
21+
- Limited execution time
22+
- Prohibit usage of third-party libraries
23+
- Parallel processing disabled
24+
- Character limit to around 3000 (I don’t remember exactly)
3025

3126
### 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.
27+
The rough idea I could come up after the analysis was:
28+
- Manipulation of the ASCII value of the letters
29+
- Simple maths, addition/subtraction, on numeric value of letters
3330

31+
Since converting this idea into a concrete algorithm was taking longer than expected, I moved to another solution.
3432

3533
### 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.
34+
The next simplest design for the solution I could come up with consisted of the following data structures:
35+
- `HashMap` to store the encrypted letters and easy access of decrypted letters
36+
- `StringBuilder` to store the deciphered text
37+
- Use Java 8 new method `String.chars()` API
3738

3839
```java
3940
public static String solnWithStrBuilder(String x) {
40-
StringBuilder stb = new StringBuilder();
41-
x.chars().forEach(i -> stb.append(decrypt(i)));
42-
return stb.toString();
41+
StringBuilder stb = new StringBuilder();
42+
x.chars().forEach(i -> stb.append(decrypt(i)));
43+
return stb.toString();
4344
}
4445
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;
46+
if ( encryptedVal >= 97 && encryptedVal <= 122) {
47+
char encodedChar;
48+
Map<Integer, Character> decryptKey = new HashMap<>();
49+
for (int i = 122, j = 97; i >= 97; i--, j++) {
50+
encodedChar = (char) i;
51+
decryptKey.put(j, encodedChar);
52+
}
53+
return decryptKey.get(encryptedVal);
54+
}
55+
else return (char)encryptedVal;
5556
}
5657
```
5758

59+
The implementation provided desired output in the local environment but it failed to pass the tests.
5860

5961
### 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.
62+
I reflected back on the constraints and refactored the code to use more primitive data structure:
63+
- Replaced `StringBuilder` with `Character Array`
6364

6465
```java
6566
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);
67+
char[] encryptedCharArr = new char[encryptedText.length()];
68+
Map<Integer, Character> decryptKey = new HashMap<>();
69+
char encodedChar;
70+
for (int i = 122, j = 97; i >= 97; i--, j++) {
71+
encodedChar = (char) i;
72+
decryptKey.put(j, encodedChar);
73+
}
74+
for (int i = 0; i < encryptedText.length(); i++) {
75+
char encryptedVal = encryptedText.charAt(i);
76+
if ( encryptedVal >= 97 && encryptedVal <= 122) {
77+
encryptedCharArr[i] = decryptKey.get((int)encryptedVal);
78+
} else encryptedCharArr[i] = encryptedVal;
79+
}
80+
return new String(encryptedCharArr);
8481
}
8582
```
8683

84+
But again the solution did not pass! At this point I was getting a little bit frustrated because there were no proper errors thrown. The output was just a list of failed tests. I lost interest after around an hour and I went back to my previous preparation method, solving challenges from other sources.
8785

8886
### 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.
87+
The next day I went back to the challenge with around 3 hours remaining. To make sure the time spent here would be worthwhile, I did some search to know more about the challenge. Only then I realized that the challenge was invitation only, and was used by Google for recruitment. Those were good enough reasons for me to continue, but then I was seriously running out of time.
9088

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.
89+
Then I searched for similar problems and solutions to find answers to these questions:
90+
- How to encrypt a letter by another
91+
- What are the standard encryption algorithms
9292

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.
93+
A simple example by [Baeldung](https://www.baeldung.com/java-caesar-cipher) answered my queries.
9494

9595
```java
9696
StringBuilder result = new StringBuilder();
9797
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-
}
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+
}
106106
}
107-
108107
return result;
109108
```
110109

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.
110+
**“Any fool can know. The point is to understand.” - Albert Einstein**
112111

113-
```java
114-
public static String encrypt(String textToEncrypt) {
115-
StringBuilder result = new StringBuilder();
112+
Knowing that standard encryption algorithm used was `Caesar Cipher`, I wanted to understand more that led to [Cryptography](https://github.com/codeanit/til/issues/43), [Cipher](https://github.com/codeanit/til/issues/107) and [more](https://github.com/codeanit/til/issues).
116113

117-
for (char character : textToEncrypt.toCharArray()) {
114+
Keeping in mind that the time was limited, I skimmed the contents.
118115

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));
116+
`A **Cipher** is a method for encrypting a message, intending to make it less readable. **Caesar Cipher** is a substitution cipher that transforms a message by shifting its letters by a given offset.`
123117

124-
} else {
125-
result.append(character);
126-
}
127-
}
118+
Baeldung's post also made me realize that understanding of the encryption process was important to decipher. Therefore, based on Baeldung's framework, I created an encryption system that generated the cipher from the challenge.
128119

129-
return new String(result);
120+
```java
121+
public static String encrypt(String textToEncrypt) {
122+
StringBuilder result = new StringBuilder();
123+
for (char character : textToEncrypt.toCharArray()) {
124+
if ( (int)character >= 97 && (int)character <= 122 ) {
125+
int originalAlphabetPosition = character - 'z';
126+
int newAlphabetPosition = ( originalAlphabetPosition + 25 ) % 26;
127+
result.append((char) ('z' - newAlphabetPosition));
128+
} else {
129+
result.append(character);
130+
}
131+
}
132+
return new String(result);
130133
}
131134
```
132135

133-
Then creating a decryption mechanism was some maths and common sense. Then again, the solution failed.
136+
After generating the exact cipher, It was pretty much easier to write a program to decipher.
134137

135138
```java
136139
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);
140+
StringBuilder result = new StringBuilder();
141+
for (char character : encryptedText.toCharArray()) {
142+
if ( (int)character >= 97 && (int)character <= 122 ) {
143+
int originalAlphabetPosition = character - 'z';
144+
int newAlphabetPosition = (originalAlphabetPosition + 25 ) % 26;
145+
result.append((char) ('z' + newAlphabetPosition));
146+
} else {
147+
result.append(character);
148+
}
149+
}
150+
return new String(result);
152151
}
153152
```
154153

154+
I submitted the solution then again it failed.
155+
156+
__Time <10mins!__
155157

156158
### 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!
159+
Refactored the code:
160+
- Replaced `StringBuilder` with `Character Array`.
158161

159162
```java
160163
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);
164+
int strLength = x.length();
165+
char[] encryptedCharArr = new char[strLength];
166+
for (int i = 0; i < strLength; i++) {
167+
int character = x.charAt(i);
168+
if ( character >= 97 && character <= 122 ) {
169+
int originalAlphabetPosition = character - 'z';
170+
int newAlphabetPosition = (originalAlphabetPosition + 25 ) % 26;
171+
encryptedCharArr[i] = (char) ('z' + newAlphabetPosition);
172+
} else {
173+
encryptedCharArr[i] = (char)character;
174+
}
178175
}
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-
}
176+
return new String(encryptedCharArr);
227177
}
228178
```
229179

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];
180+
__Yay! All Tests Passed. Time <2mins.__
236181

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-
```
182+
**Solution Submitted.**
249183

250184
### 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.
185+
The next day I reviewed the solution and recalled the overall process. Phew! It was a close call.
252186

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.
187+
I wanted to refactor the submitted code as it was done in a hurry. And prior to refactoring, I wrote `Unit Tests` to make sure that all the methods will work as before. Those tests did not need to be extensive covering all edge cases; neither should have many use cases, the solution had already passed Google's test cases.
254188

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`.
189+
Previously, I had an interview with Google in July 2019. It was more of a casual talk I had with a nice lady to see if I fit the available role at a certain location. Unfortunately, it did not work out.
256190

257-
Thank you Google!
191+
I am grateful for both opportunities but I have enjoyed this experience more may be because I'm more of a code person.
258192

193+
__Thank you Google!__
259194

260195
If code is a better language to you then please find more at [Github](https://github.com/JavaCheatsheet/codechallenge).
261-

0 commit comments

Comments
 (0)