Refactor a Caesar Cipher algorithm.

This commit is contained in:
Oleksii Trekhleb
2020-08-08 12:54:56 +02:00
parent bd7475ee19
commit 00242413a5
7 changed files with 129 additions and 72 deletions

View File

@@ -0,0 +1,30 @@
# Caesar Cipher Algorithm
In cryptography, a **Caesar cipher**, also known as **Caesar's cipher**, the **shift cipher**, **Caesar's code** or **Caesar shift**, is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of `3`, `D` would be replaced by `A`, `E` would become `B`, and so on. The method is named after Julius Caesar, who used it in his private correspondence.
![Caesar Cipher Algorithm](https://upload.wikimedia.org/wikipedia/commons/4/4a/Caesar_cipher_left_shift_of_3.svg)
## Example
The transformation can be represented by aligning two alphabets; the cipher alphabet is the plain alphabet rotated left or right by some number of positions. For instance, here is a Caesar cipher using a left rotation of three places, equivalent to a right shift of 23 (the shift parameter is used as the key):
```text
Plain: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cipher: XYZABCDEFGHIJKLMNOPQRSTUVW
```
When encrypting, a person looks up each letter of the message in the "plain" line and writes down the corresponding letter in the "cipher" line.
```text
Plaintext: THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
Ciphertext: QEB NRFZH YOLTK CLU GRJMP LSBO QEB IXWV ALD
```
## Complexity
- Time: `O(|n|)`
- Space: `O(|n|)`
## References
- [Caesar cipher on Wikipedia](https://en.wikipedia.org/wiki/Caesar_cipher)

View File

@@ -0,0 +1,40 @@
import { caesarCipherEncrypt, caesarCipherDecrypt } from '../caesarCipher';
describe('caesarCipher', () => {
it('should not change a string with zero shift', () => {
expect(caesarCipherEncrypt('abcd', 0)).toBe('abcd');
expect(caesarCipherDecrypt('abcd', 0)).toBe('abcd');
});
it('should cipher a string with different shifts', () => {
expect(caesarCipherEncrypt('abcde', 3)).toBe('defgh');
expect(caesarCipherDecrypt('defgh', 3)).toBe('abcde');
expect(caesarCipherEncrypt('abcde', 1)).toBe('bcdef');
expect(caesarCipherDecrypt('bcdef', 1)).toBe('abcde');
expect(caesarCipherEncrypt('xyz', 1)).toBe('yza');
expect(caesarCipherDecrypt('yza', 1)).toBe('xyz');
});
it('should be case insensitive', () => {
expect(caesarCipherEncrypt('ABCDE', 3)).toBe('defgh');
});
it('should correctly handle an empty strings', () => {
expect(caesarCipherEncrypt('', 3)).toBe('');
});
it('should not cipher unknown chars', () => {
expect(caesarCipherEncrypt('ab2cde', 3)).toBe('de2fgh');
expect(caesarCipherDecrypt('de2fgh', 3)).toBe('ab2cde');
});
it('should encrypt and decrypt full phrases', () => {
expect(caesarCipherEncrypt('THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG', 23))
.toBe('qeb nrfzh yoltk clu grjmp lsbo qeb ixwv ald');
expect(caesarCipherDecrypt('qeb nrfzh yoltk clu grjmp lsbo qeb ixwv ald', 23))
.toBe('the quick brown fox jumps over the lazy dog');
});
});

View File

@@ -0,0 +1,58 @@
// Create alphabet array: ['a', 'b', 'c', ..., 'z'].
const englishAlphabet = 'abcdefghijklmnopqrstuvwxyz'.split('');
/**
* Generates a cipher map out of the alphabet.
* Example with a shift 3: {'a': 'd', 'b': 'e', 'c': 'f', ...}
*
* @param {string[]} alphabet - i.e. ['a', 'b', 'c', ... , 'z']
* @param {number} shift - i.e. 3
* @return {Object} - i.e. {'a': 'd', 'b': 'e', 'c': 'f', ..., 'z': 'c'}
*/
const getCipherMap = (alphabet, shift) => {
return alphabet
.reduce((charsMap, currentChar, charIndex) => {
const charsMapClone = { ...charsMap };
// Making the shift to be cyclic (i.e. with a shift of 1 - 'z' would be mapped to 'a').
let encryptedCharIndex = (charIndex + shift) % alphabet.length;
// Support negative shifts for creating a map for decryption
// (i.e. with shift -1 - 'a' would be mapped to 'z').
if (encryptedCharIndex < 0) {
encryptedCharIndex += alphabet.length;
}
charsMapClone[currentChar] = alphabet[encryptedCharIndex];
return charsMapClone;
}, {});
};
/**
* @param {string} str
* @param {number} shift
* @param {string[]} alphabet
* @return {string}
*/
export const caesarCipherEncrypt = (str, shift, alphabet = englishAlphabet) => {
// Create a cipher map:
const cipherMap = getCipherMap(alphabet, shift);
return str
.toLowerCase()
.split('')
.map((char) => cipherMap[char] || char)
.join('');
};
/**
* @param {string} str
* @param {number} shift
* @param {string[]} alphabet
* @return {string}
*/
export const caesarCipherDecrypt = (str, shift, alphabet = englishAlphabet) => {
// Create a cipher map:
const cipherMap = getCipherMap(alphabet, -shift);
return str
.toLowerCase()
.split('')
.map((char) => cipherMap[char] || char)
.join('');
};