Skip to content

Commit dd4aae6

Browse files
François Dupiremaibin
authored andcommitted
dupirefr/[email protected] [BAEL-2452] Caesar Cipher in Java (eugenp#8209)
* Added benchmarking on larger matrices * [BAEL-3452] Added cipher algorithm * [BAEL-3452] Added deciphering * [BAEL-3452] Added break algorithm * [BAEL-3452] Finalizing break algorithm * Revert "Added benchmarking on larger matrices" This reverts commit 4ea87c0.
1 parent ef188f9 commit dd4aae6

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

algorithms-miscellaneous-5/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
<artifactId>commons-codec</artifactId>
1818
<version>${commons-codec.version}</version>
1919
</dependency>
20+
<dependency>
21+
<groupId>org.apache.commons</groupId>
22+
<artifactId>commons-math3</artifactId>
23+
<version>${commons-math3.version}</version>
24+
</dependency>
2025
<dependency>
2126
<groupId>org.projectlombok</groupId>
2227
<artifactId>lombok</artifactId>
@@ -28,6 +33,7 @@
2833
<artifactId>tradukisto</artifactId>
2934
<version>${tradukisto.version}</version>
3035
</dependency>
36+
3137
<dependency>
3238
<groupId>org.assertj</groupId>
3339
<artifactId>assertj-core</artifactId>
@@ -52,6 +58,7 @@
5258
<tradukisto.version>1.0.1</tradukisto.version>
5359
<org.assertj.core.version>3.9.0</org.assertj.core.version>
5460
<commons-codec.version>1.11</commons-codec.version>
61+
<commons-math3.version>3.6.1</commons-math3.version>
5562
</properties>
5663

5764
</project>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.baeldung.algorithms.caesarcipher;
2+
3+
import org.apache.commons.math3.stat.inference.ChiSquareTest;
4+
5+
import java.util.Arrays;
6+
import java.util.stream.IntStream;
7+
8+
public class CaesarCipher {
9+
private static final char LETTER_A = 'a';
10+
private static final char LETTER_Z = 'z';
11+
private static final int ALPHABET_SIZE = LETTER_Z - LETTER_A + 1;
12+
private static final double[] ENGLISH_LETTERS_PROBABILITIES = {0.073, 0.009, 0.030, 0.044, 0.130, 0.028, 0.016, 0.035, 0.074, 0.002, 0.003, 0.035, 0.025, 0.078, 0.074, 0.027, 0.003, 0.077, 0.063, 0.093, 0.027, 0.013, 0.016, 0.005, 0.019, 0.001};
13+
14+
public String cipher(String message, int offset) {
15+
StringBuilder result = new StringBuilder();
16+
17+
for (char character : message.toCharArray()) {
18+
if (character != ' ') {
19+
int originalAlphabetPosition = character - LETTER_A;
20+
int newAlphabetPosition = (originalAlphabetPosition + offset) % ALPHABET_SIZE;
21+
char newCharacter = (char) (LETTER_A + newAlphabetPosition);
22+
result.append(newCharacter);
23+
} else {
24+
result.append(character);
25+
}
26+
}
27+
28+
return result.toString();
29+
}
30+
31+
public String decipher(String message, int offset) {
32+
return cipher(message, ALPHABET_SIZE - (offset % ALPHABET_SIZE));
33+
}
34+
35+
public int breakCipher(String message) {
36+
return probableOffset(chiSquares(message));
37+
}
38+
39+
private double[] chiSquares(String message) {
40+
double[] expectedLettersFrequencies = expectedLettersFrequencies(message.length());
41+
42+
double[] chiSquares = new double[ALPHABET_SIZE];
43+
44+
for (int offset = 0; offset < chiSquares.length; offset++) {
45+
String decipheredMessage = decipher(message, offset);
46+
long[] lettersFrequencies = observedLettersFrequencies(decipheredMessage);
47+
double chiSquare = new ChiSquareTest().chiSquare(expectedLettersFrequencies, lettersFrequencies);
48+
chiSquares[offset] = chiSquare;
49+
}
50+
51+
return chiSquares;
52+
}
53+
54+
private long[] observedLettersFrequencies(String message) {
55+
return IntStream.rangeClosed(LETTER_A, LETTER_Z)
56+
.mapToLong(letter -> countLetter((char) letter, message))
57+
.toArray();
58+
}
59+
60+
private long countLetter(char letter, String message) {
61+
return message.chars()
62+
.filter(character -> character == letter)
63+
.count();
64+
}
65+
66+
private double[] expectedLettersFrequencies(int messageLength) {
67+
return Arrays.stream(ENGLISH_LETTERS_PROBABILITIES)
68+
.map(probability -> probability * messageLength)
69+
.toArray();
70+
}
71+
72+
private int probableOffset(double[] chiSquares) {
73+
int probableOffset = 0;
74+
for (int offset = 0; offset < chiSquares.length; offset++) {
75+
System.out.println(String.format("Chi-Square for offset %d: %.2f", offset, chiSquares[offset]));
76+
if (chiSquares[offset] < chiSquares[probableOffset]) {
77+
probableOffset = offset;
78+
}
79+
}
80+
81+
return probableOffset;
82+
}
83+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.baeldung.algorithms.caesarcipher;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
7+
class CaesarCipherUnitTest {
8+
private static final String SENTENCE = "he told me i could never teach a llama to drive";
9+
private static final String SENTENCE_SHIFTED_THREE = "kh wrog ph l frxog qhyhu whdfk d oodpd wr gulyh";
10+
private static final String SENTENCE_SHIFTED_TEN = "ro dyvn wo s myevn xofob dokmr k vvkwk dy nbsfo";
11+
12+
private CaesarCipher algorithm = new CaesarCipher();
13+
14+
@Test
15+
void givenSentenceAndShiftThree_whenCipher_thenCipheredMessageWithoutOverflow() {
16+
String cipheredSentence = algorithm.cipher(SENTENCE, 3);
17+
18+
assertThat(cipheredSentence)
19+
.isEqualTo(SENTENCE_SHIFTED_THREE);
20+
}
21+
22+
@Test
23+
void givenSentenceAndShiftTen_whenCipher_thenCipheredMessageWithOverflow() {
24+
String cipheredSentence = algorithm.cipher(SENTENCE, 10);
25+
26+
assertThat(cipheredSentence)
27+
.isEqualTo(SENTENCE_SHIFTED_TEN);
28+
}
29+
30+
@Test
31+
void givenSentenceAndShiftThirtySix_whenCipher_thenCipheredLikeTenMessageWithOverflow() {
32+
String cipheredSentence = algorithm.cipher(SENTENCE, 36);
33+
34+
assertThat(cipheredSentence)
35+
.isEqualTo(SENTENCE_SHIFTED_TEN);
36+
}
37+
38+
@Test
39+
void givenSentenceShiftedThreeAndShiftThree_whenDecipher_thenOriginalSentenceWithoutOverflow() {
40+
String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_THREE, 3);
41+
42+
assertThat(decipheredSentence)
43+
.isEqualTo(SENTENCE);
44+
}
45+
46+
@Test
47+
void givenSentenceShiftedTenAndShiftTen_whenDecipher_thenOriginalSentenceWithOverflow() {
48+
String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_TEN, 10);
49+
50+
assertThat(decipheredSentence)
51+
.isEqualTo(SENTENCE);
52+
}
53+
54+
@Test
55+
void givenSentenceShiftedTenAndShiftThirtySix_whenDecipher_thenOriginalSentenceWithOverflow() {
56+
String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_TEN, 36);
57+
58+
assertThat(decipheredSentence)
59+
.isEqualTo(SENTENCE);
60+
}
61+
62+
@Test
63+
void givenSentenceShiftedThree_whenBreakCipher_thenOriginalSentence() {
64+
int offset = algorithm.breakCipher(SENTENCE_SHIFTED_THREE);
65+
66+
assertThat(offset)
67+
.isEqualTo(3);
68+
69+
assertThat(algorithm.decipher(SENTENCE_SHIFTED_THREE, offset))
70+
.isEqualTo(SENTENCE);
71+
}
72+
73+
@Test
74+
void givenSentenceShiftedTen_whenBreakCipher_thenOriginalSentence() {
75+
int offset = algorithm.breakCipher(SENTENCE_SHIFTED_TEN);
76+
77+
assertThat(offset)
78+
.isEqualTo(10);
79+
80+
assertThat(algorithm.decipher(SENTENCE_SHIFTED_TEN, offset))
81+
.isEqualTo(SENTENCE);
82+
}
83+
}

0 commit comments

Comments
 (0)