-
Notifications
You must be signed in to change notification settings - Fork 543
affine-cipher: Add exercise #897
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Generated by exercism rust track exercise tool | ||
| # will have compiled files and executables | ||
| /target/ | ||
| **/*.rs.bk | ||
|
|
||
| # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | ||
| # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock | ||
| Cargo.lock |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| [package] | ||
| edition = "2018" | ||
| name = "affine-cipher" | ||
| version = "2.0.0" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # 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` | ||
|
|
||
| ## Rust Installation | ||
|
|
||
| Refer to the [exercism help page][help-page] for Rust installation and learning | ||
| resources. | ||
|
|
||
| ## Writing the Code | ||
|
|
||
| Execute the tests with: | ||
|
|
||
| ```bash | ||
| $ cargo test | ||
| ``` | ||
|
|
||
| All but the first test have been ignored. After you get the first test to | ||
| pass, open the tests source file which is located in the `tests` directory | ||
| and remove the `#[ignore]` flag from the next test and get the tests to pass | ||
| again. Each separate test is a function with `#[test]` flag above it. | ||
| Continue, until you pass every test. | ||
|
|
||
| If you wish to run all ignored tests without editing the tests source file, use: | ||
|
|
||
| ```bash | ||
| $ cargo test -- --ignored | ||
| ``` | ||
|
|
||
| To run a specific test, for example `some_test`, you can use: | ||
|
|
||
| ```bash | ||
| $ cargo test some_test | ||
| ``` | ||
|
|
||
| If the specific test is ignored use: | ||
|
|
||
| ```bash | ||
| $ cargo test some_test -- --ignored | ||
| ``` | ||
|
|
||
| To learn more about Rust tests refer to the [online test documentation][rust-tests] | ||
|
|
||
| Make sure to read the [Modules][modules] chapter if you | ||
| haven't already, it will help you with organizing your files. | ||
|
|
||
| ## Further improvements | ||
|
|
||
| After you have solved the exercise, please consider using the additional utilities, described in the [installation guide](https://exercism.io/tracks/rust/installation), to further refine your final solution. | ||
|
|
||
| To format your solution, inside the solution directory use | ||
|
|
||
| ```bash | ||
| cargo fmt | ||
| ``` | ||
|
|
||
| To see, if your solution contains some common ineffective use cases, inside the solution directory use | ||
|
|
||
| ```bash | ||
| cargo clippy --all-targets | ||
| ``` | ||
|
|
||
| ## Submitting the solution | ||
|
|
||
| Generally you should submit all files in which you implemented your solution (`src/lib.rs` in most cases). If you are using any external crates, please consider submitting the `Cargo.toml` file. This will make the review process faster and clearer. | ||
|
|
||
| ## Feedback, Issues, Pull Requests | ||
|
|
||
| The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help! | ||
|
|
||
| If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md). | ||
|
|
||
| [help-page]: https://exercism.io/tracks/rust/learning | ||
| [modules]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html | ||
| [cargo]: https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html | ||
| [rust-tests]: https://doc.rust-lang.org/book/ch11-02-running-tests.html | ||
|
|
||
| ## Source | ||
|
|
||
| Wikipedia [http://en.wikipedia.org/wiki/Affine_cipher](http://en.wikipedia.org/wiki/Affine_cipher) | ||
|
|
||
| ## Submitting Incomplete Solutions | ||
| It's possible to submit an incomplete solution so you can see how others have completed the exercise. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| const MODULUS: i32 = 26; | ||
|
|
||
| /// An error type for indicating problems with the input | ||
| #[derive(Debug, Eq, PartialEq)] | ||
| pub enum AffineCipherError { | ||
| NotCoprime(i32), | ||
| } | ||
|
|
||
| /// Encodes the plaintext using the affine cipher with key (`a`, `b`). Note that, rather than | ||
| /// returning a return code, the more common convention in Rust is to return a `Result`. | ||
| pub fn encode(plaintext: &str, a: i32, b: i32) -> Result<String, AffineCipherError> { | ||
| // Reject the key if `a` and `MODULUS` aren't coprime. | ||
| match modular_multiplicative_inverse(a) { | ||
| Some(_) => Ok(plaintext | ||
| .to_lowercase() | ||
| .chars() | ||
| .filter(|&ch| ch.is_ascii_alphanumeric()) | ||
| .map(|ch| encode_char(ch, a, b)) | ||
| .collect::<Vec<char>>() | ||
| .chunks(5) | ||
| .map(|slice| slice.iter().cloned().collect::<String>()) | ||
| .collect::<Vec<String>>() | ||
| .join(" ")), | ||
| None => Err(AffineCipherError::NotCoprime(a)), | ||
| } | ||
| } | ||
|
|
||
| /// Decodes the ciphertext using the affine cipher with key (`a`, `b`). Note that, rather than | ||
| /// returning a return code, the more common convention in Rust is to return a `Result`. | ||
| pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result<String, AffineCipherError> { | ||
| // Reject the key if `a` and `MODULUS` aren't coprime. | ||
| match modular_multiplicative_inverse(a) { | ||
| Some(inv) => Ok(ciphertext | ||
| .to_lowercase() | ||
| .chars() | ||
| .filter(|&ch| ch.is_ascii_alphanumeric()) | ||
| .map(|ch| decode_char(ch, inv, b)) | ||
| .collect()), | ||
| None => Err(AffineCipherError::NotCoprime(a)), | ||
| } | ||
| } | ||
|
|
||
| /// Encodes a single char with the key (`a`, `b`). The key is assumed to be valid (i.e. `a` should | ||
| /// be coprime to 26). | ||
| fn encode_char(ch: char, a: i32, b: i32) -> char { | ||
| if ch.is_digit(10) { | ||
| ch | ||
| } else { | ||
| let index = (ch as i32) - ('a' as i32); | ||
| let encoded = (a * index + b).rem_euclid(MODULUS) + 'a' as i32; | ||
| encoded as u8 as char | ||
| } | ||
| } | ||
|
|
||
| /// Decodes a single char using `inv` (the modular multiplicative inverse of `a`) and `b`. | ||
| fn decode_char(ch: char, inv: i32, b: i32) -> char { | ||
| if ch.is_digit(10) { | ||
| ch | ||
| } else { | ||
| let index = (ch as i32) - ('a' as i32); | ||
| let decoded = (inv * (index - b)).rem_euclid(MODULUS) + 'a' as i32; | ||
| decoded as u8 as char | ||
| } | ||
| } | ||
|
|
||
| /// Calculates the modular multiplicative inverse using the extended Euclidean algorithm. | ||
| /// See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm for details. | ||
| fn modular_multiplicative_inverse(a: i32) -> Option<i32> { | ||
| // `rs` corresponds to the `r_i` sequence and `ts` corresponds to the `t_i` sequence. We omit | ||
| // `s_i` since we don't need it to calculate the MMI. | ||
| let mut rs = (MODULUS, a.rem_euclid(MODULUS)); | ||
| let mut ts = (0, 1); | ||
|
|
||
| while rs.1 != 0 { | ||
| let q = rs.0.div_euclid(rs.1); | ||
|
|
||
| rs = (rs.1, rs.0 - q * rs.1); | ||
| ts = (ts.1, ts.0 - q * ts.1); | ||
| } | ||
|
|
||
| // `rs.0` gives the GCD. This must be 1 for an inverse to exist. | ||
| if rs.0 == 1 { | ||
| // ts.0 gives a number such that (s * 26 + t * a) % 26 == 1. Since (s * 26) % 26 == 0, | ||
| // we can further reduce this to (t * a) % 26 == 1. In other words, t is the MMI of a. | ||
| Some(ts.0 as i32) | ||
| } else { | ||
| None | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /// While the problem description indicates a return status of 1 should be returned on errors, | ||
| /// it is much more common to return a `Result`, so we provide an error type for the result here. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your reasoning here is precisely correct, but you should be aware of exercism/problem-specifications#1614. At your discretion, you can choose to wait for that issue to be resolved before moving forward with this PR; just let me know. If you'd prefer to get this merged as quickly as possible, that is acceptable.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can wait, and I'll update the phrasing when the specification's description.md gets updated. |
||
| #[derive(Debug, Eq, PartialEq)] | ||
| pub enum AffineCipherError { | ||
| NotCoprime(i32), | ||
| } | ||
|
|
||
| /// Encodes the plaintext using the affine cipher with key (`a`, `b`). Note that, rather than | ||
| /// returning a return code, the more common convention in Rust is to return a `Result`. | ||
| pub fn encode(plaintext: &str, a: i32, b: i32) -> Result<String, AffineCipherError> { | ||
| unimplemented!("Encode {} with the key ({}, {})", plaintext, a, b); | ||
| } | ||
|
|
||
| /// Decodes the ciphertext using the affine cipher with key (`a`, `b`). Note that, rather than | ||
| /// returning a return code, the more common convention in Rust is to return a `Result`. | ||
| pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result<String, AffineCipherError> { | ||
| unimplemented!("Decode {} with the key ({}, {})", ciphertext, a, b); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.