dupirefr/dupire.francois+pro@gmail.com [BAEL-2452] Caesar Cipher in Java (#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 4ea87c0aea44bd73be721691a3c427b03854b442.
This commit is contained in:
		
							parent
							
								
									7d52a39289
								
							
						
					
					
						commit
						6b9f2c74ee
					
				| @ -17,6 +17,11 @@ | |||||||
|             <artifactId>commons-codec</artifactId> |             <artifactId>commons-codec</artifactId> | ||||||
|             <version>${commons-codec.version}</version> |             <version>${commons-codec.version}</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.apache.commons</groupId> | ||||||
|  |             <artifactId>commons-math3</artifactId> | ||||||
|  |             <version>${commons-math3.version}</version> | ||||||
|  |         </dependency> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.projectlombok</groupId> |             <groupId>org.projectlombok</groupId> | ||||||
|             <artifactId>lombok</artifactId> |             <artifactId>lombok</artifactId> | ||||||
| @ -28,6 +33,7 @@ | |||||||
|             <artifactId>tradukisto</artifactId> |             <artifactId>tradukisto</artifactId> | ||||||
|             <version>${tradukisto.version}</version> |             <version>${tradukisto.version}</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|  | 
 | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>org.assertj</groupId> |             <groupId>org.assertj</groupId> | ||||||
|             <artifactId>assertj-core</artifactId> |             <artifactId>assertj-core</artifactId> | ||||||
| @ -52,6 +58,7 @@ | |||||||
|         <tradukisto.version>1.0.1</tradukisto.version> |         <tradukisto.version>1.0.1</tradukisto.version> | ||||||
|         <org.assertj.core.version>3.9.0</org.assertj.core.version> |         <org.assertj.core.version>3.9.0</org.assertj.core.version> | ||||||
|         <commons-codec.version>1.11</commons-codec.version> |         <commons-codec.version>1.11</commons-codec.version> | ||||||
|  |         <commons-math3.version>3.6.1</commons-math3.version> | ||||||
|     </properties> |     </properties> | ||||||
| 
 | 
 | ||||||
| </project> | </project> | ||||||
| @ -0,0 +1,83 @@ | |||||||
|  | package com.baeldung.algorithms.caesarcipher; | ||||||
|  | 
 | ||||||
|  | import org.apache.commons.math3.stat.inference.ChiSquareTest; | ||||||
|  | 
 | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.stream.IntStream; | ||||||
|  | 
 | ||||||
|  | public class CaesarCipher { | ||||||
|  |     private static final char LETTER_A = 'a'; | ||||||
|  |     private static final char LETTER_Z = 'z'; | ||||||
|  |     private static final int ALPHABET_SIZE = LETTER_Z - LETTER_A + 1; | ||||||
|  |     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}; | ||||||
|  | 
 | ||||||
|  |     public String cipher(String message, int offset) { | ||||||
|  |         StringBuilder result = new StringBuilder(); | ||||||
|  | 
 | ||||||
|  |         for (char character : message.toCharArray()) { | ||||||
|  |             if (character != ' ') { | ||||||
|  |                 int originalAlphabetPosition = character - LETTER_A; | ||||||
|  |                 int newAlphabetPosition = (originalAlphabetPosition + offset) % ALPHABET_SIZE; | ||||||
|  |                 char newCharacter = (char) (LETTER_A + newAlphabetPosition); | ||||||
|  |                 result.append(newCharacter); | ||||||
|  |             } else { | ||||||
|  |                 result.append(character); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return result.toString(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String decipher(String message, int offset) { | ||||||
|  |         return cipher(message, ALPHABET_SIZE - (offset % ALPHABET_SIZE)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int breakCipher(String message) { | ||||||
|  |         return probableOffset(chiSquares(message)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private double[] chiSquares(String message) { | ||||||
|  |         double[] expectedLettersFrequencies = expectedLettersFrequencies(message.length()); | ||||||
|  | 
 | ||||||
|  |         double[] chiSquares = new double[ALPHABET_SIZE]; | ||||||
|  | 
 | ||||||
|  |         for (int offset = 0; offset < chiSquares.length; offset++) { | ||||||
|  |             String decipheredMessage = decipher(message, offset); | ||||||
|  |             long[] lettersFrequencies = observedLettersFrequencies(decipheredMessage); | ||||||
|  |             double chiSquare = new ChiSquareTest().chiSquare(expectedLettersFrequencies, lettersFrequencies); | ||||||
|  |             chiSquares[offset] = chiSquare; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return chiSquares; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private long[] observedLettersFrequencies(String message) { | ||||||
|  |         return IntStream.rangeClosed(LETTER_A, LETTER_Z) | ||||||
|  |           .mapToLong(letter -> countLetter((char) letter, message)) | ||||||
|  |           .toArray(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private long countLetter(char letter, String message) { | ||||||
|  |         return message.chars() | ||||||
|  |           .filter(character -> character == letter) | ||||||
|  |           .count(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private double[] expectedLettersFrequencies(int messageLength) { | ||||||
|  |         return Arrays.stream(ENGLISH_LETTERS_PROBABILITIES) | ||||||
|  |           .map(probability -> probability * messageLength) | ||||||
|  |           .toArray(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private int probableOffset(double[] chiSquares) { | ||||||
|  |         int probableOffset = 0; | ||||||
|  |         for (int offset = 0; offset < chiSquares.length; offset++) { | ||||||
|  |             System.out.println(String.format("Chi-Square for offset %d: %.2f", offset, chiSquares[offset])); | ||||||
|  |             if (chiSquares[offset] < chiSquares[probableOffset]) { | ||||||
|  |                 probableOffset = offset; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return probableOffset; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,83 @@ | |||||||
|  | package com.baeldung.algorithms.caesarcipher; | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  | 
 | ||||||
|  | class CaesarCipherUnitTest { | ||||||
|  |     private static final String SENTENCE = "he told me i could never teach a llama to drive"; | ||||||
|  |     private static final String SENTENCE_SHIFTED_THREE = "kh wrog ph l frxog qhyhu whdfk d oodpd wr gulyh"; | ||||||
|  |     private static final String SENTENCE_SHIFTED_TEN = "ro dyvn wo s myevn xofob dokmr k vvkwk dy nbsfo"; | ||||||
|  | 
 | ||||||
|  |     private CaesarCipher algorithm = new CaesarCipher(); | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceAndShiftThree_whenCipher_thenCipheredMessageWithoutOverflow() { | ||||||
|  |         String cipheredSentence = algorithm.cipher(SENTENCE, 3); | ||||||
|  | 
 | ||||||
|  |         assertThat(cipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE_SHIFTED_THREE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceAndShiftTen_whenCipher_thenCipheredMessageWithOverflow() { | ||||||
|  |         String cipheredSentence = algorithm.cipher(SENTENCE, 10); | ||||||
|  | 
 | ||||||
|  |         assertThat(cipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE_SHIFTED_TEN); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceAndShiftThirtySix_whenCipher_thenCipheredLikeTenMessageWithOverflow() { | ||||||
|  |         String cipheredSentence = algorithm.cipher(SENTENCE, 36); | ||||||
|  | 
 | ||||||
|  |         assertThat(cipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE_SHIFTED_TEN); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceShiftedThreeAndShiftThree_whenDecipher_thenOriginalSentenceWithoutOverflow() { | ||||||
|  |         String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_THREE, 3); | ||||||
|  | 
 | ||||||
|  |         assertThat(decipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceShiftedTenAndShiftTen_whenDecipher_thenOriginalSentenceWithOverflow() { | ||||||
|  |         String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_TEN, 10); | ||||||
|  | 
 | ||||||
|  |         assertThat(decipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceShiftedTenAndShiftThirtySix_whenDecipher_thenOriginalSentenceWithOverflow() { | ||||||
|  |         String decipheredSentence = algorithm.decipher(SENTENCE_SHIFTED_TEN, 36); | ||||||
|  | 
 | ||||||
|  |         assertThat(decipheredSentence) | ||||||
|  |           .isEqualTo(SENTENCE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceShiftedThree_whenBreakCipher_thenOriginalSentence() { | ||||||
|  |         int offset = algorithm.breakCipher(SENTENCE_SHIFTED_THREE); | ||||||
|  | 
 | ||||||
|  |         assertThat(offset) | ||||||
|  |           .isEqualTo(3); | ||||||
|  | 
 | ||||||
|  |         assertThat(algorithm.decipher(SENTENCE_SHIFTED_THREE, offset)) | ||||||
|  |           .isEqualTo(SENTENCE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     void givenSentenceShiftedTen_whenBreakCipher_thenOriginalSentence() { | ||||||
|  |         int offset = algorithm.breakCipher(SENTENCE_SHIFTED_TEN); | ||||||
|  | 
 | ||||||
|  |         assertThat(offset) | ||||||
|  |           .isEqualTo(10); | ||||||
|  | 
 | ||||||
|  |         assertThat(algorithm.decipher(SENTENCE_SHIFTED_TEN, offset)) | ||||||
|  |           .isEqualTo(SENTENCE); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user