-
-
Notifications
You must be signed in to change notification settings - Fork 637
✨ Add new exercise : Affine Cipher #887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
SleeplessByte
merged 7 commits into
exercism:master
from
TomPradat:feature/new-exercise-affine-cipher
Nov 3, 2020
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
11ec507
:sparkles: Add new exercise : Affine Cipher
TomPradat 267fc6f
:ok_hand:
TomPradat 1eb7281
:ok_hand:
TomPradat 234c3a8
:ok_hand:
TomPradat 9006152
:ok_hand: Move exercise in config.json file
TomPradat cd9b2e4
:ok_hand: Fix affine-cipher.js formatting
TomPradat ee5fef3
:ok_hand: Rename const m
TomPradat File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"root": true, | ||
"parser": "babel-eslint", | ||
"parserOptions": { | ||
"ecmaVersion": 7, | ||
"sourceType": "module" | ||
}, | ||
"globals": { | ||
"BigInt": true | ||
}, | ||
"env": { | ||
"es6": true, | ||
"node": true, | ||
"jest": true | ||
}, | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:import/errors", | ||
"plugin:import/warnings" | ||
], | ||
"rules": { | ||
"linebreak-style": "off", | ||
|
||
"import/extensions": "off", | ||
"import/no-default-export": "off", | ||
"import/no-unresolved": "off", | ||
"import/prefer-default-export": "off" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
audit=false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
## Affine Cipher | ||
|
||
Create an implementation of the affine cipher, | ||
an ancient encryption system created in the Middle East. | ||
|
||
The affine cipher is a type of monoalphabetic substitution cipher. | ||
Each character is mapped to its numeric equivalent, encrypted with | ||
a mathematical function and then converted to the letter relating to | ||
its new numeric value. Although all monoalphabetic ciphers are weak, | ||
the affine cypher is much stronger than the atbash cipher, | ||
because it has many more keys. | ||
|
||
the encryption function is: | ||
|
||
`E(x) = (ax + b) mod m` | ||
- where `x` is the letter's index from 0 - length of alphabet - 1 | ||
- `m` is the length of the alphabet. For the roman alphabet `m == 26`. | ||
- and `a` and `b` make the key | ||
|
||
the decryption function is: | ||
|
||
`D(y) = a^-1(y - b) mod m` | ||
- where `y` is the numeric value of an encrypted letter, ie. `y = E(x)` | ||
- it is important to note that `a^-1` is the modular multiplicative inverse | ||
of `a mod m` | ||
- the modular multiplicative inverse of `a` only exists if `a` and `m` are | ||
coprime. | ||
|
||
To find the MMI of `a`: | ||
|
||
`an mod m = 1` | ||
- where `n` is the modular multiplicative inverse of `a mod m` | ||
|
||
More information regarding how to find a Modular Multiplicative Inverse | ||
and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) | ||
|
||
Because automatic decryption fails if `a` is not coprime to `m` your | ||
program should return status 1 and `"Error: a and m must be coprime."` | ||
if they are not. Otherwise it should encode or decode with the | ||
provided key. | ||
|
||
The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and | ||
`b` as the magnitude results in a static displacement of the letters. | ||
This is much less secure than a full implementation of the affine cipher. | ||
|
||
Ciphertext is written out in groups of fixed length, the traditional group | ||
size being 5 letters, and punctuation is excluded. This is to make it | ||
harder to guess things based on word boundaries. | ||
|
||
## Examples | ||
|
||
- Encoding `test` gives `ybty` with the key a=5 b=7 | ||
- Decoding `ybty` gives `test` with the key a=5 b=7 | ||
- Decoding `ybty` gives `lqul` with the wrong key a=11 b=7 | ||
- Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx` | ||
- gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13 | ||
- Encoding `test` with the key a=18 b=13 | ||
- gives `Error: a and m must be coprime.` | ||
- because a and m are not relatively prime | ||
|
||
### Examples of finding a Modular Multiplicative Inverse (MMI) | ||
|
||
- simple example: | ||
- `9 mod 26 = 9` | ||
- `9 * 3 mod 26 = 27 mod 26 = 1` | ||
- `3` is the MMI of `9 mod 26` | ||
- a more complicated example: | ||
- `15 mod 26 = 15` | ||
- `15 * 7 mod 26 = 105 mod 26 = 1` | ||
- `7` is the MMI of `15 mod 26` | ||
|
||
## Setup | ||
|
||
Go through the setup instructions for Javascript to install the necessary | ||
dependencies: | ||
|
||
[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation) | ||
|
||
## Requirements | ||
|
||
Please `cd` into exercise directory before running all below commands. | ||
|
||
Install assignment dependencies: | ||
|
||
```bash | ||
$ npm install | ||
``` | ||
|
||
## Making the test suite pass | ||
|
||
Execute the tests with: | ||
|
||
```bash | ||
$ npm test | ||
``` | ||
|
||
In the test suites all tests but the first have been skipped. | ||
|
||
Once you get a test passing, you can enable the next one by changing `xtest` to | ||
`test`. | ||
|
||
|
||
## Submitting Solutions | ||
|
||
Once you have a solution ready, you can submit it using: | ||
|
||
```bash | ||
exercism submit allergies.js | ||
``` | ||
|
||
## Submitting Incomplete Solutions | ||
|
||
It's possible to submit an incomplete solution so you can see how others have | ||
completed the exercise. | ||
|
||
## Exercise Source Credits | ||
|
||
Jumpstart Lab Warm-up [http://jumpstartlab.com](http://jumpstartlab.com) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const encode = (phrase, key) => { | ||
throw new Error("Remove this statement and implement this function"); | ||
}; | ||
|
||
export const decode = (phrase, key) => { | ||
throw new Error("Remove this statement and implement this function"); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { encode, decode } from './affine-cipher'; | ||
|
||
describe('Affine cipher', () => { | ||
describe('encode', () => { | ||
test('encode yes', () => { | ||
expect(encode('yes', { a: 5, b: 7 })).toBe('xbt'); | ||
}); | ||
|
||
xtest('encode no', () => { | ||
expect(encode('no', { a: 15, b: 18 })).toBe('fu'); | ||
}); | ||
|
||
xtest('encode OMG', () => { | ||
expect(encode('OMG', { a: 21, b: 3 })).toBe('lvz'); | ||
}); | ||
|
||
xtest('encode O M G', () => { | ||
expect(encode('O M G', { a: 25, b: 47 })).toBe('hjp'); | ||
}); | ||
|
||
xtest('encode mindblowingly', () => { | ||
expect(encode('mindblowingly', { a: 11, b: 15 })).toBe('rzcwa gnxzc dgt'); | ||
}); | ||
|
||
xtest('encode numbers', () => { | ||
expect(encode('Testing,1 2 3, testing.', { a: 3, b: 4 })).toBe('jqgjc rw123 jqgjc rw'); | ||
}); | ||
|
||
xtest('encode deep thought', () => { | ||
expect(encode('Truth is fiction.', { a: 5, b: 17 })).toBe('iynia fdqfb ifje'); | ||
}); | ||
|
||
xtest('encode all the letters', () => { | ||
expect(encode('The quick brown fox jumps over the lazy dog.', { a: 17, b: 33 })).toBe('swxtj npvyk lruol iejdc blaxk swxmh qzglf'); | ||
}); | ||
|
||
xtest('encode with a not coprime to m', () => { | ||
expect(() => { | ||
encode('This is a test.', { a: 6, b: 17 }); | ||
}).toThrowError('a and m must be coprime.') | ||
}); | ||
}); | ||
describe('decode', () => { | ||
test('decode exercism', () => { | ||
expect(decode('tytgn fjr', { a: 3, b: 7 })).toBe('exercism'); | ||
}); | ||
|
||
xtest('decode a sentence', () => { | ||
expect(decode('qdwju nqcro muwhn odqun oppmd aunwd o', { a: 19, b: 16 })).toBe('anobstacleisoftenasteppingstone'); | ||
}); | ||
|
||
xtest('decode numbers', () => { | ||
expect(decode('odpoz ub123 odpoz ub', { a: 25, b: 7 })).toBe('testing123testing'); | ||
}); | ||
|
||
xtest('decode all the letters', () => { | ||
expect(decode('swxtj npvyk lruol iejdc blaxk swxmh qzglf', { a: 17, b: 33 })).toBe('thequickbrownfoxjumpsoverthelazydog'); | ||
}); | ||
|
||
xtest('decode with no spaces in input', () => { | ||
expect(decode('swxtjnpvyklruoliejdcblaxkswxmhqzglf', { a: 17, b: 33 })).toBe('thequickbrownfoxjumpsoverthelazydog'); | ||
}); | ||
|
||
xtest('decode with too many spaces', () => { | ||
expect(decode('vszzm cly yd cg qdp', { a: 15, b: 16 })).toBe('jollygreengiant'); | ||
}); | ||
|
||
xtest('decode with a not coprime to m', () => { | ||
expect(() => { | ||
decode('Test', { a: 13, b: 5 }); | ||
}).toThrowError('a and m must be coprime.'); | ||
}); | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module.exports = { | ||
presets: [ | ||
[ | ||
"@babel/preset-env", | ||
{ | ||
targets: { | ||
node: "current", | ||
}, | ||
useBuiltIns: "entry", | ||
corejs: 3, | ||
}, | ||
], | ||
], | ||
plugins: ["@babel/plugin-syntax-bigint"], | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz'; | ||
const ALPHABET_LENGTH = ALPHABET.length; | ||
|
||
const areCoprimes = (a, b) => { | ||
for (let i = Math.min(a, b); i > 1; i--) { | ||
if (a % i == 0 && b % i == 0) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
const checkCoprime = (a, b) => { | ||
if (!areCoprimes(a, b)) { | ||
throw new Error('a and m must be coprime.'); | ||
} | ||
} | ||
|
||
const isNumber = (candidate) => { | ||
return !isNaN(Number(candidate)); | ||
} | ||
|
||
const findMMI = (a) => { | ||
let i = 1; | ||
|
||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
i++; | ||
|
||
if ((a * i - 1) % ALPHABET_LENGTH === 0) { | ||
return i; | ||
} | ||
} | ||
} | ||
|
||
const positiveModulo = (a, b) => { | ||
return ((a % b) + b) % b; | ||
} | ||
|
||
const groupBy = (elements, groupLength) => { | ||
const result = [[]]; | ||
let i = 0; | ||
|
||
elements.forEach(el => { | ||
if (result[i] && result[i].length < groupLength ) { | ||
result[i].push(el); | ||
} else { | ||
i++; | ||
result[i] = [el]; | ||
} | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
export const encode = (phrase, { a, b }) => { | ||
checkCoprime(a, ALPHABET_LENGTH); | ||
|
||
let encodedText = ''; | ||
|
||
phrase | ||
.toLowerCase() | ||
.split('') | ||
.filter(char => char !== ' ') | ||
.forEach(char => { | ||
if (ALPHABET.includes(char)) { | ||
const x = ALPHABET.indexOf(char); | ||
const encodedIndex = (a * x + b) % ALPHABET_LENGTH; | ||
|
||
encodedText += ALPHABET[encodedIndex]; | ||
} else if (isNumber(char)) { | ||
encodedText += char; | ||
} | ||
}); | ||
|
||
return groupBy(encodedText.split(''), 5) | ||
.map(group => group.join('')) | ||
.join(' '); | ||
}; | ||
|
||
export const decode = (phrase, { a, b }) => { | ||
checkCoprime(a, ALPHABET_LENGTH); | ||
|
||
const mmi = findMMI(a); | ||
|
||
return phrase | ||
.split('') | ||
.filter(char => char !== ' ') | ||
.map(char => { | ||
if (isNumber(char)) { | ||
return char; | ||
} | ||
|
||
const y = ALPHABET.indexOf(char); | ||
const decodedIndex = positiveModulo(mmi * (y - b), ALPHABET_LENGTH); | ||
|
||
return ALPHABET[decodedIndex]; | ||
}) | ||
.join(''); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.