From 95059828f704f6fb616d12845d62c89f660efcd6 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 1 Aug 2019 03:34:20 +0800 Subject: [PATCH 01/13] added affine-cipher --- exercises/affine-cipher/README.md | 109 +++++++++++++++++++ exercises/affine-cipher/affine-cipher.R | 8 ++ exercises/affine-cipher/example.R | 49 +++++++++ exercises/affine-cipher/test_affine-cipher.R | 36 ++++++ 4 files changed, 202 insertions(+) create mode 100644 exercises/affine-cipher/README.md create mode 100644 exercises/affine-cipher/affine-cipher.R create mode 100644 exercises/affine-cipher/example.R create mode 100644 exercises/affine-cipher/test_affine-cipher.R diff --git a/exercises/affine-cipher/README.md b/exercises/affine-cipher/README.md new file mode 100644 index 00000000..1a339e5e --- /dev/null +++ b/exercises/affine-cipher/README.md @@ -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 + - `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 `.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_.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_.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. diff --git a/exercises/affine-cipher/affine-cipher.R b/exercises/affine-cipher/affine-cipher.R new file mode 100644 index 00000000..5bf857b4 --- /dev/null +++ b/exercises/affine-cipher/affine-cipher.R @@ -0,0 +1,8 @@ +encrypt <- function(message, a, b) { + +} + + +decrypt <- function(encryption, a, b) { + +} diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R new file mode 100644 index 00000000..9fbe6cf5 --- /dev/null +++ b/exercises/affine-cipher/example.R @@ -0,0 +1,49 @@ +# 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") + } + + # E(x) = (ax + b) mod m + return(paste(letters[ ((a * (match(strsplit(tolower(gsub(" ", "", message)), "")[[1]], letters) - 1) + 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)) + } + + # must check a and m are coprime + if (gcd(a, m) != 1) { + stop("a and 26 must be co-prime") + } + + # 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) + } + + # D(y) = a^-1(y - b) mod m + return(paste(letters[((mmi(a, m) * ((match(strsplit(gsub(" ", "", encryption), "")[[1]], letters) - 1) - b)) %% m) + 1], collapse = "")) +} diff --git a/exercises/affine-cipher/test_affine-cipher.R b/exercises/affine-cipher/test_affine-cipher.R new file mode 100644 index 00000000..aab468ff --- /dev/null +++ b/exercises/affine-cipher/test_affine-cipher.R @@ -0,0 +1,36 @@ +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") From 0ba2c40be7a6fd199f1b0c7b1abba9397fa84f24 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 1 Aug 2019 03:44:51 +0800 Subject: [PATCH 02/13] Changes to config file. Math heavy so put difficult of 5. Do not think any other question is similar. --- config.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config.json b/config.json index c04da2bb..2abed0cf 100644 --- a/config.json +++ b/config.json @@ -392,6 +392,18 @@ "control_flow_conditionals", "text_formatting" ] + }, + { + "slug": "affine-cipher", + "uuid": "9bc3f040-9e4b-4ed0-b23c-3ae565e83a59", + "core": false, + "difficulty": 5, + "topics": [ + "control_flow_conditionals", + "math", + "recursion", + "algorithms" + ] } ] } From 3e2825b07bfdf1377c533b6ea44fa8cffca50d9c Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 7 Aug 2019 03:11:02 +0800 Subject: [PATCH 03/13] For lintr bot, made `return()` lines more succinct --- exercises/affine-cipher/example.R | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 9fbe6cf5..8c0d7b0a 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -13,8 +13,12 @@ encrypt <- function(message, a, b) { stop("a and 26 must be co-prime") } + parsedMessage <- tolower(gsub(" ", "", message)) # removed whitespace & lower-cased + splitList <- strsplit(parsedMessage, "")[[1]] # list of letters + x <- match(splitList, letters) - 1 # index of letters + # E(x) = (ax + b) mod m - return(paste(letters[ ((a * (match(strsplit(tolower(gsub(" ", "", message)), "")[[1]], letters) - 1) + b) %% m) + 1], collapse = "")) + return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) } # decrypts encryption @@ -44,6 +48,10 @@ decrypt <- function(encryption, a, b) { return(1) } + parsedEncryption <- gsub(" ", "", encryption) # removed whitespace + splitList <- strsplit(parsedEncryption, "")[[1]] # list of letters + y <- (match(splitList, letters) - 1) # index of letters + # D(y) = a^-1(y - b) mod m - return(paste(letters[((mmi(a, m) * ((match(strsplit(gsub(" ", "", encryption), "")[[1]], letters) - 1) - b)) %% m) + 1], collapse = "")) + return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) } From f9be9b23ec28792ac8e5600d6def558466f21954 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 7 Aug 2019 03:34:37 +0800 Subject: [PATCH 04/13] Small changes to fix linting errors Weird. lintbot said all variable names must be lowercased but did not address all my cases (ie i used splitList but it did not detect it) --- exercises/affine-cipher/example.R | 12 ++++++------ exercises/affine-cipher/test_affine-cipher.R | 10 ++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 8c0d7b0a..72cb0c69 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -13,9 +13,9 @@ encrypt <- function(message, a, b) { stop("a and 26 must be co-prime") } - parsedMessage <- tolower(gsub(" ", "", message)) # removed whitespace & lower-cased - splitList <- strsplit(parsedMessage, "")[[1]] # list of letters - x <- match(splitList, letters) - 1 # index of letters + parsedmessage <- tolower(gsub(" ", "", message)) # removed whitespace & lower-cased + splitlist <- strsplit(parsedmessage, "")[[1]] # list of letters + x <- match(splitlist, letters) - 1 # index of letters # E(x) = (ax + b) mod m return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) @@ -48,9 +48,9 @@ decrypt <- function(encryption, a, b) { return(1) } - parsedEncryption <- gsub(" ", "", encryption) # removed whitespace - splitList <- strsplit(parsedEncryption, "")[[1]] # list of letters - y <- (match(splitList, letters) - 1) # index of letters + parsedencryption <- gsub(" ", "", encryption) # removed whitespace + splitlist <- strsplit(parsedencryption, "")[[1]] # list of letters + y <- (match(splitlist, letters) - 1) # index of letters # D(y) = a^-1(y - b) mod m return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) diff --git a/exercises/affine-cipher/test_affine-cipher.R b/exercises/affine-cipher/test_affine-cipher.R index aab468ff..60bf07d3 100644 --- a/exercises/affine-cipher/test_affine-cipher.R +++ b/exercises/affine-cipher/test_affine-cipher.R @@ -21,12 +21,18 @@ test_that("encrypt() checks that a is coprime with m", { # 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") + 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") + expect_identical( + decrypt("kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx", 19, 13), + "thequickbrownfoxjumpsoverthelazydog" + ) }) test_that("decrypt() checks that a is coprime with m", { From f9accd7bb5f74184ce783b66fdc8bc1f1f85d489 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 7 Aug 2019 12:13:08 +0800 Subject: [PATCH 05/13] fixed spaces AGAIN --- exercises/affine-cipher/example.R | 18 ++++++++++++------ exercises/affine-cipher/test_affine-cipher.R | 4 ++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 72cb0c69..70143e1c 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -13,9 +13,12 @@ encrypt <- function(message, a, b) { stop("a and 26 must be co-prime") } - parsedmessage <- tolower(gsub(" ", "", message)) # removed whitespace & lower-cased - splitlist <- strsplit(parsedmessage, "")[[1]] # list of letters - x <- match(splitlist, letters) - 1 # index of letters + # removed whitespace & lower-cased + parsedmessage <- tolower(gsub(" ", "", message)) + # list of letters + splitlist <- strsplit(parsedmessage, "")[[1]] + # index of letters + x <- match(splitlist, letters) - 1 # E(x) = (ax + b) mod m return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) @@ -48,9 +51,12 @@ decrypt <- function(encryption, a, b) { return(1) } - parsedencryption <- gsub(" ", "", encryption) # removed whitespace - splitlist <- strsplit(parsedencryption, "")[[1]] # list of letters - y <- (match(splitlist, letters) - 1) # index of letters + # removed whitespace + parsedencryption <- gsub(" ", "", encryption) + # list of letters + splitlist <- strsplit(parsedencryption, "")[[1]] + # index of letters + y <- (match(splitlist, letters) - 1) # D(y) = a^-1(y - b) mod m return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) diff --git a/exercises/affine-cipher/test_affine-cipher.R b/exercises/affine-cipher/test_affine-cipher.R index 60bf07d3..29e91e6c 100644 --- a/exercises/affine-cipher/test_affine-cipher.R +++ b/exercises/affine-cipher/test_affine-cipher.R @@ -24,7 +24,7 @@ test_that("decrypt() returns correct string", { expect_identical( decrypt("kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx", 19, 13), "thequickbrownfoxjumpsoverthelazydog" - ) + ) }) test_that("decrypt() accounts for whitespace", { @@ -32,7 +32,7 @@ test_that("decrypt() accounts for whitespace", { expect_identical( decrypt("kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx", 19, 13), "thequickbrownfoxjumpsoverthelazydog" - ) + ) }) test_that("decrypt() checks that a is coprime with m", { From 14f6423bf9eb1612c267d4dcf6132e43ee628eb8 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 12 Aug 2019 14:28:41 +0800 Subject: [PATCH 06/13] changed config difficulty, modularise files. will add test later --- exercises/affine-cipher/README.md | 36 ++++----- exercises/affine-cipher/affine-cipher.R | 63 ++++++++++++++- exercises/affine-cipher/dump.R | 23 ++++++ exercises/affine-cipher/example.R | 80 ++++++++++---------- exercises/affine-cipher/test_affine-cipher.R | 7 ++ 5 files changed, 151 insertions(+), 58 deletions(-) create mode 100644 exercises/affine-cipher/dump.R diff --git a/exercises/affine-cipher/README.md b/exercises/affine-cipher/README.md index 1a339e5e..aca7b739 100644 --- a/exercises/affine-cipher/README.md +++ b/exercises/affine-cipher/README.md @@ -22,9 +22,9 @@ The algorithm is as below: 1. 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 + * where `x` is the letter's index from 0 to length of alphabet - 1 + * `m` is the length of the alphabet. For the roman alphabet `m == 26`. + * and `a` and `b` make the key Alphabet | x a | 0 | @@ -38,10 +38,10 @@ 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 + * 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 + * the modular multiplicative inverse of `a` only exists if `a` and `m` are coprime. Alphabet | y @@ -56,33 +56,33 @@ etc.. | etc.. 3. To find the MMI of `a`: `an mod m = 1` - - where `n` is the modular multiplicative inverse of `a mod m` + * 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 +* `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` + * 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 + * 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: + 1. 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: + 2. a more complicated example: - `15 mod 26 = 15` - `15 * 7 mod 26 = 105 mod 26 = 1` - `7` is the MMI of `15 mod 26` diff --git a/exercises/affine-cipher/affine-cipher.R b/exercises/affine-cipher/affine-cipher.R index 5bf857b4..7a850fbe 100644 --- a/exercises/affine-cipher/affine-cipher.R +++ b/exercises/affine-cipher/affine-cipher.R @@ -1,8 +1,67 @@ -encrypt <- function(message, a, b) { +# normalise function +# - remove whitespace +# - make all lower case +normalise<-function(text){ + return(tolower(gsub(" ", "", text))) } +#lookup index of letter +lookupIndex<-function(normalisedtext){ + # list of letters + letterslist <- strsplit(normalisedtext, "")[[1]] + # index of letters + return(match(letterslist, letters) - 1) +} -decrypt <- function(encryption, a, b) { +#find gcd +gcd <- function(x, y) { + r <- x %% y + return(ifelse(r, gcd(y, r), y)) +} + +# 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) +} + +# encrypts plaintext +encrypt <- function(plaintext, a, b) { + m <- 26 + + # must check a and m are coprime + if (gcd(a, m) != 1) { + stop(paste('a=',a,' and m=',m,'is coprime')) + } + + #normalise text input + normalisedplaintext<-normalise(plaintext) + x<-lookupIndex(normalisedplaintext) + + + # E(x) = (ax + b) mod m + return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) +} +# decrypts encryption +decrypt <- function(encryption, a, b) { + m <- 26 + + # must check a and m are coprime + if (gcd(a, m) != 1) { + stop("a and 26 must be co-prime") + } + + normalisedencryption<-normalise(encryption) + y<-lookupIndex(normalisedencryption) + + # D(y) = a^-1(y - b) mod m + return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) } diff --git a/exercises/affine-cipher/dump.R b/exercises/affine-cipher/dump.R new file mode 100644 index 00000000..5c01b342 --- /dev/null +++ b/exercises/affine-cipher/dump.R @@ -0,0 +1,23 @@ + +encrypt() +# removed whitespace & lower-cased +parsedplaintext <- tolower(gsub(" ", "", plaintext)) +# list of letters +splitlist <- strsplit(parsedplaintext, "")[[1]] +# index of letters +x <- match(splitlist, letters) - 1 + +decrypt() +# removed whitespace +parsedencryption <- gsub(" ", "", encryption) + + +encrypt <- function(plaintext, a, b) { + +} + + +decrypt <- function(encryption, a, b) { + +} + diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 70143e1c..658ef1f7 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -1,24 +1,50 @@ -# 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)) +# normalise function +# - remove whitespace +# - make all lower case +normalise<-function(text){ + return(tolower(gsub(" ", "", text))) +} + +#lookup index of letter +lookupIndex<-function(normalisedtext){ + # list of letters + splitlist <- strsplit(parsedplaintext, "")[[1]] + # index of letters + return(match(splitlist, letters) - 1) +} + +#find gcd +gcd <- function(x, y) { + r <- x %% y + return(ifelse(r, gcd(y, r), y)) +} + +# 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) +} + +# encrypts plaintext +encrypt <- function(plaintext, a, b) { + m <- 26 # must check a and m are coprime if (gcd(a, m) != 1) { - stop("a and 26 must be co-prime") + stop(paste('a=',a,' and m=',m,'is coprime')) } + + #normalise text input + normalisedplaintext<-normalise(plaintext) + x<-lookupIndex(normalisedencryption) - # removed whitespace & lower-cased - parsedmessage <- tolower(gsub(" ", "", message)) - # list of letters - splitlist <- strsplit(parsedmessage, "")[[1]] - # index of letters - x <- match(splitlist, letters) - 1 # E(x) = (ax + b) mod m return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) @@ -28,35 +54,13 @@ encrypt <- function(message, a, b) { 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)) - } - # must check a and m are coprime if (gcd(a, m) != 1) { stop("a and 26 must be co-prime") } - # 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) + normalisedencryption<-normalise(plaintext) + y<-lookupIndex(normalisedencryption) # D(y) = a^-1(y - b) mod m return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) diff --git a/exercises/affine-cipher/test_affine-cipher.R b/exercises/affine-cipher/test_affine-cipher.R index 29e91e6c..320897ce 100644 --- a/exercises/affine-cipher/test_affine-cipher.R +++ b/exercises/affine-cipher/test_affine-cipher.R @@ -1,6 +1,13 @@ source("./affine-cipher.R") library(testthat) + +#check normalise function + +#check mmi function + +# check gcd function + # check encrypt() function test_that("encrypt() returns correct string", { expect_identical(encrypt("test", 5, 7), "ybty") From 3b5d281d4c66458da9f33a057a1f0c9288a56f86 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 12 Aug 2019 16:02:48 +0800 Subject: [PATCH 07/13] clean up --- exercises/affine-cipher/README.md | 47 +++++++++++------ exercises/affine-cipher/affine-cipher.R | 69 ++++--------------------- exercises/affine-cipher/example.R | 40 ++++---------- 3 files changed, 52 insertions(+), 104 deletions(-) diff --git a/exercises/affine-cipher/README.md b/exercises/affine-cipher/README.md index aca7b739..911b88d3 100644 --- a/exercises/affine-cipher/README.md +++ b/exercises/affine-cipher/README.md @@ -8,21 +8,28 @@ 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: +To make an affine cipher, you must create two main 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` +1. `encrypt(plaintext,a,b)`: A function to encrpt a plaintext/message with keys `a` and `b` +2. `decrypt(encryption,a,b)`: A function to decrypt an encrypted plaintext/message with keys `a` and `b` -## Algorithm +In order to help to break down the problem into smaller pieces, you must create 4 utility functions. + +1. `normalise(text)`: A function to remove whitepsace in text and change all letter cases to lower case. +2. `lookupindex(normalisedtext)`: A function to return an index of a letter from a lookup table. +3. `gcd(x,y)`: A function to compute the greatest common divisor of two numbers. +4. `mmi(a,m)`: A function to compute the modulo multiplicative inverse `a mod m`. + +Note: These utility functions are not required to pass the test. + +## Main 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 to length of alphabet - 1 + * where `x` is the letter's index from 0 to (length of alphabet - 1) * `m` is the length of the alphabet. For the roman alphabet `m == 26`. * and `a` and `b` make the key @@ -38,7 +45,7 @@ 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)` + * where `y` is the encrypted letter's index from 0 to (length of alphabet - 1) * 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 @@ -52,21 +59,23 @@ 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` +3. To find the GCD of two numbers: + +The easiest way to execute this is to compute this is to use the Euclidean Algorithm. The benefits of using the euclidean algorithm is that it is commutative, i.e. `gcd(x,y) = gcd(y,x)`. + +4. 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 +* all messages are normalised (white space is removed and all letters converted to lowercase) before applying the encrypt algorithm +* all encryptions are normalised (white space is removed and all letters converted to lowercase) before applying the decrypt algorithm -## Examples +## Examples of Main Algorithm * Encoding `test` gives `ybty` with the key a=5 b=7 * Decoding `ybty` gives `test` with the key a=5 b=7 @@ -75,7 +84,13 @@ You must also check for the following: - 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 Greatest Common Divisor (GCD) + * The greatest common divisor of two prime numbers is 1 + * The greatest common divisor of 8 and 24 is 8 + * The greatest commond divisor of 60 and 9 is 3 + ### Examples of finding a Modular Multiplicative Inverse (MMI) 1. simple example: diff --git a/exercises/affine-cipher/affine-cipher.R b/exercises/affine-cipher/affine-cipher.R index 7a850fbe..edd9eed4 100644 --- a/exercises/affine-cipher/affine-cipher.R +++ b/exercises/affine-cipher/affine-cipher.R @@ -1,67 +1,18 @@ - -# normalise function -# - remove whitespace -# - make all lower case normalise<-function(text){ - return(tolower(gsub(" ", "", text))) -} - -#lookup index of letter -lookupIndex<-function(normalisedtext){ - # list of letters - letterslist <- strsplit(normalisedtext, "")[[1]] - # index of letters - return(match(letterslist, letters) - 1) -} - -#find gcd -gcd <- function(x, y) { - r <- x %% y - return(ifelse(r, gcd(y, r), y)) -} - -# 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) -} - -# encrypts plaintext -encrypt <- function(plaintext, a, b) { - m <- 26 - - # must check a and m are coprime - if (gcd(a, m) != 1) { - stop(paste('a=',a,' and m=',m,'is coprime')) - } - - #normalise text input - normalisedplaintext<-normalise(plaintext) - x<-lookupIndex(normalisedplaintext) +} +lookupindex<-function(normalisedtext){ - # E(x) = (ax + b) mod m - return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) } - -# decrypts encryption -decrypt <- function(encryption, a, b) { - m <- 26 +gcd<-function(x,y){ - # must check a and m are coprime - if (gcd(a, m) != 1) { - stop("a and 26 must be co-prime") - } +} +mmi<-function(a,m){ - normalisedencryption<-normalise(encryption) - y<-lookupIndex(normalisedencryption) +} +encrypt<-function(plaintext,a,b){ - # D(y) = a^-1(y - b) mod m - return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) } +decrypt<-function(encryption,a,b){ + +} \ No newline at end of file diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 658ef1f7..0b8cdf98 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -1,27 +1,17 @@ - -# normalise function -# - remove whitespace -# - make all lower case normalise<-function(text){ return(tolower(gsub(" ", "", text))) } -#lookup index of letter -lookupIndex<-function(normalisedtext){ - # list of letters - splitlist <- strsplit(parsedplaintext, "")[[1]] - # index of letters - return(match(splitlist, letters) - 1) +lookupindex<-function(normalisedtext){ + letterslist <- strsplit(normalisedtext, "")[[1]] + return(match(letterslist, letters) - 1) } -#find gcd gcd <- function(x, y) { r <- x %% y return(ifelse(r, gcd(y, r), y)) } -# 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) { @@ -32,36 +22,28 @@ mmi <- function(a, m) { return(1) } -# encrypts plaintext encrypt <- function(plaintext, a, b) { m <- 26 - - # must check a and m are coprime + if (gcd(a, m) != 1) { stop(paste('a=',a,' and m=',m,'is coprime')) } - #normalise text input normalisedplaintext<-normalise(plaintext) - x<-lookupIndex(normalisedencryption) - - - # E(x) = (ax + b) mod m + x<-lookupindex(normalisedplaintext) + return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) } -# decrypts encryption decrypt <- function(encryption, a, b) { m <- 26 - - # must check a and m are coprime + if (gcd(a, m) != 1) { stop("a and 26 must be co-prime") } - - normalisedencryption<-normalise(plaintext) - y<-lookupIndex(normalisedencryption) - - # D(y) = a^-1(y - b) mod m + + normalisedencryption<-normalise(encryption) + y<-lookupindex(normalisedencryption) + return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) } From eabc734e511b34202c3c58696326d85428c85f3e Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 12 Aug 2019 16:10:37 +0800 Subject: [PATCH 08/13] remove comments in test file --- exercises/affine-cipher/test_affine-cipher.R | 9 --------- 1 file changed, 9 deletions(-) diff --git a/exercises/affine-cipher/test_affine-cipher.R b/exercises/affine-cipher/test_affine-cipher.R index 320897ce..5baf9cfd 100644 --- a/exercises/affine-cipher/test_affine-cipher.R +++ b/exercises/affine-cipher/test_affine-cipher.R @@ -1,14 +1,6 @@ source("./affine-cipher.R") library(testthat) - -#check normalise function - -#check mmi function - -# check gcd function - -# check encrypt() function test_that("encrypt() returns correct string", { expect_identical(encrypt("test", 5, 7), "ybty") }) @@ -25,7 +17,6 @@ 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( From 8d7e56b7a37eefd2a63439677b58fbfa97433dc3 Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 12 Aug 2019 16:19:22 +0800 Subject: [PATCH 09/13] removed dump file. solving lintr bot stuff. --- exercises/affine-cipher/affine-cipher.R | 28 ++++++++++++------------- exercises/affine-cipher/dump.R | 23 -------------------- exercises/affine-cipher/example.R | 26 +++++++++++------------ 3 files changed, 27 insertions(+), 50 deletions(-) delete mode 100644 exercises/affine-cipher/dump.R diff --git a/exercises/affine-cipher/affine-cipher.R b/exercises/affine-cipher/affine-cipher.R index edd9eed4..ce90056b 100644 --- a/exercises/affine-cipher/affine-cipher.R +++ b/exercises/affine-cipher/affine-cipher.R @@ -1,18 +1,18 @@ -normalise<-function(text){ - +normalise <- function(text) { + } -lookupindex<-function(normalisedtext){ - +lookupindex <- function(normalisedtext) { + } -gcd<-function(x,y){ - -} -mmi<-function(a,m){ - +gcd <- function(x, y) { + } -encrypt<-function(plaintext,a,b){ - +mmi <- function(a, m) { + +} +encrypt <- function(plaintext, a, b) { + +} +decrypt <- function(encryption, a, b) { + } -decrypt<-function(encryption,a,b){ - -} \ No newline at end of file diff --git a/exercises/affine-cipher/dump.R b/exercises/affine-cipher/dump.R deleted file mode 100644 index 5c01b342..00000000 --- a/exercises/affine-cipher/dump.R +++ /dev/null @@ -1,23 +0,0 @@ - -encrypt() -# removed whitespace & lower-cased -parsedplaintext <- tolower(gsub(" ", "", plaintext)) -# list of letters -splitlist <- strsplit(parsedplaintext, "")[[1]] -# index of letters -x <- match(splitlist, letters) - 1 - -decrypt() -# removed whitespace -parsedencryption <- gsub(" ", "", encryption) - - -encrypt <- function(plaintext, a, b) { - -} - - -decrypt <- function(encryption, a, b) { - -} - diff --git a/exercises/affine-cipher/example.R b/exercises/affine-cipher/example.R index 0b8cdf98..3ba4b1ba 100644 --- a/exercises/affine-cipher/example.R +++ b/exercises/affine-cipher/example.R @@ -1,8 +1,8 @@ -normalise<-function(text){ +normalise <- function(text) { return(tolower(gsub(" ", "", text))) } -lookupindex<-function(normalisedtext){ +lookupindex <- function(normalisedtext) { letterslist <- strsplit(normalisedtext, "")[[1]] return(match(letterslist, letters) - 1) } @@ -24,26 +24,26 @@ mmi <- function(a, m) { encrypt <- function(plaintext, a, b) { m <- 26 - + if (gcd(a, m) != 1) { - stop(paste('a=',a,' and m=',m,'is coprime')) + stop(paste("a=", a, " and m=", m, "is coprime")) } - - normalisedplaintext<-normalise(plaintext) - x<-lookupindex(normalisedplaintext) - + + normalisedplaintext <- normalise(plaintext) + x <- lookupindex(normalisedplaintext) + return(paste(letters[ ((a * x + b) %% m) + 1], collapse = "")) } decrypt <- function(encryption, a, b) { m <- 26 - + if (gcd(a, m) != 1) { stop("a and 26 must be co-prime") } - - normalisedencryption<-normalise(encryption) - y<-lookupindex(normalisedencryption) - + + normalisedencryption <- normalise(encryption) + y <- lookupindex(normalisedencryption) + return(paste(letters[((mmi(a, m) * (y - b)) %% m) + 1], collapse = "")) } From 6a6378995dbbe36dafe453dec3411b514cd8f941 Mon Sep 17 00:00:00 2001 From: Katrin Leinweber Date: Thu, 15 Aug 2019 23:30:44 +0200 Subject: [PATCH 10/13] Configure affine-cipher according to "practical option" See https://github.com/exercism/r/pull/125/files#r312783196 --- config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 2abed0cf..82f0787b 100644 --- a/config.json +++ b/config.json @@ -397,7 +397,8 @@ "slug": "affine-cipher", "uuid": "9bc3f040-9e4b-4ed0-b23c-3ae565e83a59", "core": false, - "difficulty": 5, + "unlocked_by": "prime-factors", + "difficulty": 4, "topics": [ "control_flow_conditionals", "math", From 0ffbef211d3470c9950e2a9410ebee04dff8bcf1 Mon Sep 17 00:00:00 2001 From: Katrin Leinweber Date: Thu, 15 Aug 2019 23:40:21 +0200 Subject: [PATCH 11/13] Separate the functions visually --- exercises/affine-cipher/affine-cipher.R | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exercises/affine-cipher/affine-cipher.R b/exercises/affine-cipher/affine-cipher.R index ce90056b..ed1cc2b1 100644 --- a/exercises/affine-cipher/affine-cipher.R +++ b/exercises/affine-cipher/affine-cipher.R @@ -1,18 +1,23 @@ normalise <- function(text) { } + lookupindex <- function(normalisedtext) { } + gcd <- function(x, y) { } + mmi <- function(a, m) { } + encrypt <- function(plaintext, a, b) { } + decrypt <- function(encryption, a, b) { } From 380eff6bf0e4458824c87038d4dc26107190b56d Mon Sep 17 00:00:00 2001 From: Katrin Leinweber Date: Thu, 15 Aug 2019 23:45:23 +0200 Subject: [PATCH 12/13] Fix typos & minor formatting issues --- exercises/affine-cipher/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/exercises/affine-cipher/README.md b/exercises/affine-cipher/README.md index 911b88d3..ee10f736 100644 --- a/exercises/affine-cipher/README.md +++ b/exercises/affine-cipher/README.md @@ -10,8 +10,8 @@ its new numeric value. To make an affine cipher, you must create two main functions: -1. `encrypt(plaintext,a,b)`: A function to encrpt a plaintext/message with keys `a` and `b` -2. `decrypt(encryption,a,b)`: A function to decrypt an encrypted plaintext/message with keys `a` and `b` +1. `encrypt(plaintext, a, b)`: A function to encrpt a plaintext/message with keys `a` and `b` +2. `decrypt(encryption, a, b)`: A function to decrypt an encrypted plaintext/message with keys `a` and `b` In order to help to break down the problem into smaller pieces, you must create 4 utility functions. @@ -30,7 +30,7 @@ The algorithm is as below: `E(x) = (ax + b) mod m` * where `x` is the letter's index from 0 to (length of alphabet - 1) - * `m` is the length of the alphabet. For the roman alphabet `m == 26`. + * `m` is the length of the alphabet. For the Roman alphabet `m == 26`. * and `a` and `b` make the key Alphabet | x @@ -40,7 +40,7 @@ c | 2 | d | 3 | e | 4 | f | 5 | -etc.. | etc.. +etc. | etc. 2. the decryption function is: @@ -58,11 +58,11 @@ c | 2 | d | 3 | e | 4 | f | 5 | -etc.. | etc.. +etc. | etc. 3. To find the GCD of two numbers: -The easiest way to execute this is to compute this is to use the Euclidean Algorithm. The benefits of using the euclidean algorithm is that it is commutative, i.e. `gcd(x,y) = gcd(y,x)`. +The easiest way to execute this is to compute the Euclidean Algorithm. The benefits of using the Euclidean algorithm is that it is commutative, i.e. `gcd(x,y) = gcd(y,x)`. 4. To find the MMI of `a`: @@ -89,7 +89,7 @@ You must also check for the following: * The greatest common divisor of two prime numbers is 1 * The greatest common divisor of 8 and 24 is 8 - * The greatest commond divisor of 60 and 9 is 3 + * The greatest common divisor of 60 and 9 is 3 ### Examples of finding a Modular Multiplicative Inverse (MMI) From 42d12622136794667855489f17f8af66eed983b1 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 17 Aug 2019 11:59:40 +0800 Subject: [PATCH 13/13] Change unlocked by to rotational-cipher(core). Prime-factors (not core) --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 82f0787b..9d21a0c8 100644 --- a/config.json +++ b/config.json @@ -397,7 +397,7 @@ "slug": "affine-cipher", "uuid": "9bc3f040-9e4b-4ed0-b23c-3ae565e83a59", "core": false, - "unlocked_by": "prime-factors", + "unlocked_by": "rotational-cipher", "difficulty": 4, "topics": [ "control_flow_conditionals",