Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,21 @@
"vectors"
]
},
{
"slug": "affine-cipher",
"uuid": "2a1dcf38-ec05-4b24-a2e2-2e5b3595f3f0",
"core": false,
"unlocked_by": "atbash-cipher",
"difficulty": 4,
"topics": [
"ascii",
"chars",
"iterators",
"primitive_types",
Comment thread
ktomsic marked this conversation as resolved.
"str_vs_string",
"strings"
]
},
{
"slug": "variable-length-quantity",
"uuid": "f1371a9c-c2a4-4fc6-a5fd-3a57c4af16fa",
Expand Down
8 changes: 8 additions & 0 deletions exercises/affine-cipher/.gitignore
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
4 changes: 4 additions & 0 deletions exercises/affine-cipher/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[package]
edition = "2018"
name = "affine-cipher"
version = "2.0.0"
150 changes: 150 additions & 0 deletions exercises/affine-cipher/README.md
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.
89 changes: 89 additions & 0 deletions exercises/affine-cipher/example.rs
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
}
}
18 changes: 18 additions & 0 deletions exercises/affine-cipher/src/lib.rs
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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
}
Loading