-
-
Notifications
You must be signed in to change notification settings - Fork 40
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
base: main
Are you sure you want to change the base?
Affin cipher fix #125
Changes from 5 commits
9505982
0ba2c40
3e2825b
f9be9b2
f9accd7
14f6423
3b5d281
eabc734
8d7e56b
6a63789
0ffbef2
380eff6
cc0c5e9
42d1262
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,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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
encrypt <- function(message, a, b) { | ||
|
||
} | ||
|
||
|
||
decrypt <- function(encryption, a, b) { | ||
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. Does 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. Yes. Agreed. |
||
|
||
} |
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") | ||
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. Should 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. Yes okay |
||
} | ||
|
||
# removed whitespace & lower-cased | ||
parsedmessage <- tolower(gsub(" ", "", message)) | ||
# list of letters | ||
splitlist <- strsplit(parsedmessage, "")[[1]] | ||
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. 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)) | ||
} | ||
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. Duplicate of L5-9, see https://github.com/exercism/r/pull/125/files#r312707113 |
||
|
||
# must check a and m are coprime | ||
if (gcd(a, m) != 1) { | ||
stop("a and 26 must be co-prime") | ||
} | ||
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. Duplicate of L11-14, see https://github.com/exercism/r/pull/125/files#r312707113 |
||
|
||
# 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 = "")) | ||
} |
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") |
There was a problem hiding this comment.
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
ortournament
? Those are4
s, so we should have an argument here to introduce5
.There was a problem hiding this comment.
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
;-)There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.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.
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.