Skip to content
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

Affin cipher fix #125

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,18 @@
"control_flow_conditionals",
"text_formatting"
]
},
{
"slug": "affine-cipher",
"uuid": "9bc3f040-9e4b-4ed0-b23c-3ae565e83a59",
"core": false,
"difficulty": 5,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you consider this substantially more difficult than crypto-square or tournament? Those are 4s, so we should have an argument here to introduce 5.

Copy link
Contributor

@katrinleinweber katrinleinweber Aug 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: I use https://jonmcalder.shinyapps.io/exercism-config-viz/ BTW to visualise where a new exercise might fit into the track structure. Please feel free to suggest an unlocked_by ;-)

Copy link
Contributor Author

@tintinthong tintinthong Aug 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katrinleinweber (For me) I do not necessarily feel that affine-cipher is difficult at all (tournament is more difficult tho). However, I do feel that the reader contends with a high overhead of needing to find out gcd and MMI as pre-requisites of solving the question. As expected I checked in python and crypto-square was 1 whereas tournament and affine-cipher was 5. The rust track does not have affine-cipher but rates tournament and crypto-square both as 4. I am happy to rate affine-cipher at 4, however, to the non-mathematically inclined will experience it to be a bit harder than normal.

Copy link
Contributor Author

@tintinthong tintinthong Aug 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally. for the student. We would want to break it up into smaller pieces and make each piece as pre-requisite. These are the unlocked_by tree; in brackets it is difficulty.

  1. Ideal Option
prime-factors (3)-> gcd (3) -> mmi (2) --> affine-cipher(3)
crypto-square (4) -> affine-cipher(3) 

If these pre-requisites exist for affine-cipher, it's difficulty should dampen. And the true task of affine cipher is just to put gcd and mmi together and also handle the normalisation-- that's all.

  1. Practical Option
prime-factors (3) ->affine-cipher(4)

The reason I include the practical option is because. 1) unlocked_by cannot have a many-to-one dependence (correct me if I am wrong). 2) Do not want to add too many new exercises just for the sake of affine-cipher.

In addition, I should add the pre-requisite of being able to solve a gcd function in the README.md. I noticed I have not made it as clear as how I have done for the MMI.

"topics": [
"control_flow_conditionals",
"math",
"recursion",
"algorithms"
]
}
]
}
109 changes: 109 additions & 0 deletions exercises/affine-cipher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# 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.

To make an affine cipher, you must create two functions:

1. `encrypt(message,a,b)`: A function to encrpt a message with keys `a` and `b`
2. `decrypt(encryption,a,b)`: A function to decrypt an encrypted messsage with keys `a` and `b`

## Algorithm

The algorithm is as below:

** Note: `m` equals 26. You should take `m` as a constant. **

1. the encryption function is:

`E(x) = (ax + b) mod m`
- where `x` is the letter's index from 0 - length of alphabet - 1
katrinleinweber marked this conversation as resolved.
Show resolved Hide resolved
- `m` is the length of the alphabet. For the roman alphabet `m == 26`.
- and `a` and `b` make the key

Alphabet | x
a | 0 |
b | 1 |
c | 2 |
d | 3 |
e | 4 |
f | 5 |
etc.. | etc..

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

Alphabet | y
a | 0 |
b | 1 |
c | 2 |
d | 3 |
e | 4 |
f | 5 |
etc.. | etc..

3. To find the MMI of `a`:

`an mod m = 1`
- where `n` is the modular multiplicative inverse of `a mod m`

## Caveats

You must also check for the following:
- `a` is coprime with `m`, otherwise, an error is returned. The statement `a` and `m` are coprime means that the greatest common divisor of `a` and `m` equals 1.
- all encryptions have whitespace removed before applying the algorithm
- all encryptions have whitespace removed before applying the algorithm
- all messages are converted lower case before applying the algorithm

## 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.`

### 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`


## Installation
See [this guide](https://exercism.io/tracks/r/installation) for instructions on how to setup your local R environment.

## How to implement your solution
In each problem folder, there is a file named `<exercise_name>.R` containing a function that returns a `NULL` value. Place your implementation inside the body of the function.

## How to run tests

Inside of RStudio, simply execute the `test_<exercise_name>.R` script. This can be conveniently done with [testthat's `auto_test` function](https://www.rdocumentation.org/packages/testthat/topics/auto_test). Because Exercism code and tests are in the same folder, use this same path for both `code_path` and `test_path` parameters. On the command-line, you can also run `Rscript test_<exercise_name>.R`.


## Source

1. [Lecture Notes](http://pi.math.cornell.edu/~kozdron/Teaching/Cornell/135Summer06/Handouts/affine.pdf)
2. [Wikipedia](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)
3. [Modular Multiplicative Inverse](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
8 changes: 8 additions & 0 deletions exercises/affine-cipher/affine-cipher.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
encrypt <- function(message, a, b) {

}


decrypt <- function(encryption, a, b) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does encryption mean the ciphertext here? If yes, please consider renaming it. message -> plaintext might also be a good idea then, because those are the domain terms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Agreed.


}
63 changes: 63 additions & 0 deletions exercises/affine-cipher/example.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# encrypts message
encrypt <- function(message, a, b) {
m <- 26

# computes the greatest common divisor of numbers x and y
gcd <- function(x, y) {
r <- x %% y
return(ifelse(r, gcd(y, r), y))
}

# must check a and m are coprime
if (gcd(a, m) != 1) {
stop("a and 26 must be co-prime")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should 26 be hard-coded here? I think stop can act like paste in that m could be inserted dynamically here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes okay

}

# removed whitespace & lower-cased
parsedmessage <- tolower(gsub(" ", "", message))
# list of letters
splitlist <- strsplit(parsedmessage, "")[[1]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be variables names here, that don't require a comment above them?

# index of letters
x <- match(splitlist, letters) - 1

# E(x) = (ax + b) mod m
return(paste(letters[ ((a * x + b) %% m) + 1], collapse = ""))
}

# decrypts encryption
decrypt <- function(encryption, a, b) {
m <- 26

# computes the greatest common divisor of numbers x and y
gcd <- function(x, y) {
r <- x %% y
return(ifelse(r, gcd(y, r), y))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# must check a and m are coprime
if (gcd(a, m) != 1) {
stop("a and 26 must be co-prime")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# computes the modulo multiplicative inverse of a^m
# a^-1 = mmi( a mod m)=mmi(a,m)
mmi <- function(a, m) {
a <- a %% m
for (x in 1:m) {
if ((a * x) %% m == 1) {
return(x)
}
}
return(1)
}

# removed whitespace
parsedencryption <- gsub(" ", "", encryption)
# list of letters
splitlist <- strsplit(parsedencryption, "")[[1]]
# index of letters
y <- (match(splitlist, letters) - 1)
katrinleinweber marked this conversation as resolved.
Show resolved Hide resolved

# D(y) = a^-1(y - b) mod m
return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = ""))
}
42 changes: 42 additions & 0 deletions exercises/affine-cipher/test_affine-cipher.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
source("./affine-cipher.R")
library(testthat)

# check encrypt() function
test_that("encrypt() returns correct string", {
expect_identical(encrypt("test", 5, 7), "ybty")
})

test_that("encrypt() accounts for whitespace", {
expect_identical(encrypt("te st ", 5, 7), "ybty")
})

test_that("encrypt() accounts for case-sensitivity", {
expect_identical(encrypt("TeST", 5, 7), "ybty")
})

test_that("encrypt() checks that a is coprime with m", {
expect_error(encrypt("jknkasd", 18, 13))
})

# check decrypt() function
test_that("decrypt() returns correct string", {
expect_identical(decrypt("ybty", 5, 7), "test")
expect_identical(
decrypt("kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx", 19, 13),
"thequickbrownfoxjumpsoverthelazydog"
)
})

test_that("decrypt() accounts for whitespace", {
expect_identical(decrypt(" ybt y", 5, 7), "test")
expect_identical(
decrypt("kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx", 19, 13),
"thequickbrownfoxjumpsoverthelazydog"
)
})

test_that("decrypt() checks that a is coprime with m", {
expect_error(decrypt("jknkasd", 18, 13))
})

message("All tests passed for exercise: affine-cipher")