From 00242413a55a8fb69432d825d2bb593fa9c71c40 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Sat, 8 Aug 2020 12:54:56 +0200 Subject: [PATCH] Refactor a Caesar Cipher algorithm. --- README.md | 1 + .../cryptography/caesar-cipher/README.md | 30 ++++++++++ .../__test__/caesarCipher.test.js | 40 +++++++++++++ .../caesar-cipher/caesarCipher.js | 58 +++++++++++++++++++ src/algorithms/string/caeserCipher/README.md | 15 ----- .../__test__/caeserCipher.test.js | 27 --------- .../string/caeserCipher/caeserCipher.js | 30 ---------- 7 files changed, 129 insertions(+), 72 deletions(-) create mode 100644 src/algorithms/cryptography/caesar-cipher/README.md create mode 100644 src/algorithms/cryptography/caesar-cipher/__test__/caesarCipher.test.js create mode 100644 src/algorithms/cryptography/caesar-cipher/caesarCipher.js delete mode 100644 src/algorithms/string/caeserCipher/README.md delete mode 100644 src/algorithms/string/caeserCipher/__test__/caeserCipher.test.js delete mode 100644 src/algorithms/string/caeserCipher/caeserCipher.js diff --git a/README.md b/README.md index 17a7e7b0..2a2c50ae 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ a set of rules that precisely define a sequence of operations. * `A` [Travelling Salesman Problem](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city * **Cryptography** * `B` [Polynomial Hash](src/algorithms/cryptography/polynomial-hash) - rolling hash function based on polynomial + * `B` [Caesar Cipher](src/algorithms/cryptography/caesar-cipher) - simple substitution cipher * **Machine Learning** * `B` [NanoNeuron](https://github.com/trekhleb/nano-neuron) - 7 simple JS functions that illustrate how machines can actually learn (forward/backward propagation) * **Uncategorized** diff --git a/src/algorithms/cryptography/caesar-cipher/README.md b/src/algorithms/cryptography/caesar-cipher/README.md new file mode 100644 index 00000000..428b5a97 --- /dev/null +++ b/src/algorithms/cryptography/caesar-cipher/README.md @@ -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) diff --git a/src/algorithms/cryptography/caesar-cipher/__test__/caesarCipher.test.js b/src/algorithms/cryptography/caesar-cipher/__test__/caesarCipher.test.js new file mode 100644 index 00000000..90521ced --- /dev/null +++ b/src/algorithms/cryptography/caesar-cipher/__test__/caesarCipher.test.js @@ -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'); + }); +}); diff --git a/src/algorithms/cryptography/caesar-cipher/caesarCipher.js b/src/algorithms/cryptography/caesar-cipher/caesarCipher.js new file mode 100644 index 00000000..ba922990 --- /dev/null +++ b/src/algorithms/cryptography/caesar-cipher/caesarCipher.js @@ -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(''); +}; diff --git a/src/algorithms/string/caeserCipher/README.md b/src/algorithms/string/caeserCipher/README.md deleted file mode 100644 index 9bd84796..00000000 --- a/src/algorithms/string/caeserCipher/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# caeserCipher Algorithm - -caeserCipher Algorithm is a type of substitution algorithm 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 - -## Complexity - -- **Time:** `O(|n|)` -- **Space:** `O(|n|)` -## Example - -The the following string `abcde` which is shifted by 1 will change to `bcdef` - -## References - -- [Wikipedia](https://en.wikipedia.org/wiki/Caesar_cipher) diff --git a/src/algorithms/string/caeserCipher/__test__/caeserCipher.test.js b/src/algorithms/string/caeserCipher/__test__/caeserCipher.test.js deleted file mode 100644 index f852de99..00000000 --- a/src/algorithms/string/caeserCipher/__test__/caeserCipher.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import caeserCipher from '../caeserCipher'; - -describe('caeserCipher', () => { - it('should subtitute each character by shifting up the alphabet by a given integer', () => { - - - expect(caeserCipher('abcd', 1)).toBe('bcde'); - }); - - - it('should wrap back to the beginning of the alphabet if it shifts more than the 26 english alphabets', () => { - - - expect(caeserCipher('xyz', 1)).toBe('yza'); - }); - it('should only shift letters and ignore non-alphabetic characters', () => { - - expect(caeserCipher('gurer ner 9 qbtf!', 13)).toBe('there are 9 dogs!'); - }); - - it('should ignore case sensitivity', () => { - - expect(caeserCipher('ABCD', 13)).toBe('bcde'); - }); - - -}) \ No newline at end of file diff --git a/src/algorithms/string/caeserCipher/caeserCipher.js b/src/algorithms/string/caeserCipher/caeserCipher.js deleted file mode 100644 index 47d6abf1..00000000 --- a/src/algorithms/string/caeserCipher/caeserCipher.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @param {string} string - * @param {number} shift - * @return {string} result - */ - -export default function caeserCipher(string, shift) { - // convert all alphabets in english language to an array - const alphabetArr = "abcdefghijklmnopqrstuvwxyz".split("") - // converting all the alphabets in string to lowercase - string = string.toLowerCase() - - let result = "" - for (let i = 0; i < string.length; i++) { - const currentChar = string[i] - // checking index of character in the english alphabets - const idx = alphabetArr.indexOf(currentChar) - // skip character if the character is not an alphabet - if (idx === -1) { - result += currentChar - continue; - } - //wrap up index, incase the next shift is beyond the 26th character - const encodedIdx = (idx + shift) % 26 - result += alphabetArr[encodedIdx] - - } - // return the result of the shifted string - return result -}