Skip to content

Commit 70c4703

Browse files
marcelweikumvaeng
andauthored
Added "affine-cipher" exercise (#973)
* Added alphametics exercise to the track * Added affine-cipher exercise to the track * Changed difficulty in config.json * Minor indent change in example.cpp * Initial state of main and changed guard * Removed alphametic for new branch * Added prerequisites and practices * Changed prerequisites and practices * Run pre-commit locally --------- Co-authored-by: Christian Willner <[email protected]>
1 parent d126427 commit 70c4703

File tree

12 files changed

+18358
-0
lines changed

12 files changed

+18358
-0
lines changed

config.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,22 @@
12451245
],
12461246
"difficulty": 4
12471247
},
1248+
{
1249+
"slug": "affine-cipher",
1250+
"name": "Affine Cipher",
1251+
"uuid": "61374c16-0a99-4616-9244-a38919eafbe1",
1252+
"practices": [
1253+
"math",
1254+
"exceptions"
1255+
],
1256+
"prerequisites": [
1257+
"functions",
1258+
"comparisons",
1259+
"loops",
1260+
"booleans"
1261+
],
1262+
"difficulty": 6
1263+
},
12481264
{
12491265
"slug": "alphametics",
12501266
"name": "Alphametics",
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Instructions
2+
3+
Create an implementation of the affine cipher, an ancient encryption system created in the Middle East.
4+
5+
The affine cipher is a type of monoalphabetic substitution cipher.
6+
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.
7+
Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys.
8+
9+
[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic "
10+
11+
## Encryption
12+
13+
The encryption function is:
14+
15+
```text
16+
E(x) = (ai + b) mod m
17+
```
18+
19+
Where:
20+
21+
- `i` is the letter's index from `0` to the length of the alphabet - 1.
22+
- `m` is the length of the alphabet.
23+
For the Latin alphabet `m` is `26`.
24+
- `a` and `b` are integers which make up the encryption key.
25+
26+
Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]).
27+
In case `a` is not coprime to `m`, your program should indicate that this is an error.
28+
Otherwise it should encrypt or decrypt with the provided key.
29+
30+
For the purpose of this exercise, digits are valid input but they are not encrypted.
31+
Spaces and punctuation characters are excluded.
32+
Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters.
33+
This is to make it harder to guess encrypted text based on word boundaries.
34+
35+
## Decryption
36+
37+
The decryption function is:
38+
39+
```text
40+
D(y) = (a^-1)(y - b) mod m
41+
```
42+
43+
Where:
44+
45+
- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)`
46+
- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m`
47+
- the modular multiplicative inverse only exists if `a` and `m` are coprime.
48+
49+
The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`:
50+
51+
```text
52+
ax mod m = 1
53+
```
54+
55+
More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi].
56+
57+
## General Examples
58+
59+
- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7`
60+
- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7`
61+
- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7`
62+
- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13`
63+
- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime
64+
65+
## Example of finding a Modular Multiplicative Inverse (MMI)
66+
67+
Finding MMI for `a = 15`:
68+
69+
- `(15 * x) mod 26 = 1`
70+
- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1`
71+
- `7` is the MMI of `15 mod 26`
72+
73+
[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
74+
[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": [
3+
"marcelweikum"
4+
],
5+
"files": {
6+
"solution": [
7+
"affine_cipher.cpp",
8+
"affine_cipher.h"
9+
],
10+
"test": [
11+
"affine_cipher_test.cpp"
12+
],
13+
"example": [
14+
".meta/example.cpp",
15+
".meta/example.h"
16+
]
17+
},
18+
"blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.",
19+
"source": "Wikipedia",
20+
"source_url": "https://en.wikipedia.org/wiki/Affine_cipher"
21+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <cctype>
2+
#include <stdexcept>
3+
4+
#include "affine_cipher.h"
5+
6+
namespace affine_cipher {
7+
8+
static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
9+
10+
static int mod(int x, int m) {
11+
int r = x % m;
12+
return r < 0 ? r + m : r;
13+
}
14+
15+
static int modinv(int a, int m) {
16+
int t = 0, newt = 1;
17+
int r = m, newr = a;
18+
while (newr != 0) {
19+
int q = r / newr;
20+
t -= q * newt;
21+
std::swap(t, newt);
22+
r -= q * newr;
23+
std::swap(r, newr);
24+
}
25+
if (r != 1) throw std::invalid_argument("a and m must be coprime.");
26+
return t < 0 ? t + m : t;
27+
}
28+
29+
std::string encode(const std::string& text, int a, int b) {
30+
const int m = 26;
31+
if (gcd(a, m) != 1) throw std::invalid_argument("a and m must be coprime.");
32+
std::string out;
33+
out.reserve(text.size());
34+
for (char ch : text) {
35+
if (std::isalpha(ch)) {
36+
int x = std::tolower(ch) - 'a';
37+
out += char('a' + mod(a * x + b, m));
38+
} else if (std::isdigit(ch)) {
39+
out += ch;
40+
}
41+
}
42+
for (int i = 5; i < (int)out.size(); i += 6) {
43+
out.insert(out.begin() + i, ' ');
44+
}
45+
return out;
46+
}
47+
48+
std::string decode(const std::string& text, int a, int b) {
49+
const int m = 26;
50+
if (gcd(a, m) != 1) throw std::invalid_argument("a and m must be coprime.");
51+
int inv = modinv(a, m);
52+
std::string out;
53+
out.reserve(text.size());
54+
for (char ch : text) {
55+
if (std::isalpha(ch)) {
56+
int y = std::tolower(ch) - 'a';
57+
out += char('a' + mod(inv * (y - b), m));
58+
} else if (std::isdigit(ch)) {
59+
out += ch;
60+
}
61+
}
62+
return out;
63+
}
64+
65+
} // namespace affine_cipher
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace affine_cipher {
6+
7+
std::string encode(const std::string& input, int a, int b);
8+
std::string decode(const std::string& input, int a, int b);
9+
10+
} // namespace affine_cipher
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[2ee1d9af-1c43-416c-b41b-cefd7d4d2b2a]
13+
description = "encode -> encode yes"
14+
15+
[785bade9-e98b-4d4f-a5b0-087ba3d7de4b]
16+
description = "encode -> encode no"
17+
18+
[2854851c-48fb-40d8-9bf6-8f192ed25054]
19+
description = "encode -> encode OMG"
20+
21+
[bc0c1244-b544-49dd-9777-13a770be1bad]
22+
description = "encode -> encode O M G"
23+
24+
[381a1a20-b74a-46ce-9277-3778625c9e27]
25+
description = "encode -> encode mindblowingly"
26+
27+
[6686f4e2-753b-47d4-9715-876fdc59029d]
28+
description = "encode -> encode numbers"
29+
30+
[ae23d5bd-30a8-44b6-afbe-23c8c0c7faa3]
31+
description = "encode -> encode deep thought"
32+
33+
[c93a8a4d-426c-42ef-9610-76ded6f7ef57]
34+
description = "encode -> encode all the letters"
35+
36+
[0673638a-4375-40bd-871c-fb6a2c28effb]
37+
description = "encode -> encode with a not coprime to m"
38+
39+
[3f0ac7e2-ec0e-4a79-949e-95e414953438]
40+
description = "decode -> decode exercism"
41+
42+
[241ee64d-5a47-4092-a5d7-7939d259e077]
43+
description = "decode -> decode a sentence"
44+
45+
[33fb16a1-765a-496f-907f-12e644837f5e]
46+
description = "decode -> decode numbers"
47+
48+
[20bc9dce-c5ec-4db6-a3f1-845c776bcbf7]
49+
description = "decode -> decode all the letters"
50+
51+
[623e78c0-922d-49c5-8702-227a3e8eaf81]
52+
description = "decode -> decode with no spaces in input"
53+
54+
[58fd5c2a-1fd9-4563-a80a-71cff200f26f]
55+
description = "decode -> decode with too many spaces"
56+
57+
[b004626f-c186-4af9-a3f4-58f74cdb86d5]
58+
description = "decode -> decode with a not coprime to m"
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Get the exercise name from the current directory
2+
get_filename_component(exercise ${CMAKE_CURRENT_SOURCE_DIR} NAME)
3+
4+
# Basic CMake project
5+
cmake_minimum_required(VERSION 3.5.1)
6+
7+
# Name the project after the exercise
8+
project(${exercise} CXX)
9+
10+
# Get a source filename from the exercise name by replacing -'s with _'s
11+
string(REPLACE "-" "_" file ${exercise})
12+
13+
# Implementation could be only a header
14+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file}.cpp)
15+
set(exercise_cpp ${file}.cpp)
16+
else()
17+
set(exercise_cpp "")
18+
endif()
19+
20+
# Use the common Catch library?
21+
if(EXERCISM_COMMON_CATCH)
22+
# For Exercism track development only
23+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h $<TARGET_OBJECTS:catchlib>)
24+
elseif(EXERCISM_TEST_SUITE)
25+
# The Exercism test suite is being run, the Docker image already
26+
# includes a pre-built version of Catch.
27+
find_package(Catch2 REQUIRED)
28+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h)
29+
target_link_libraries(${exercise} PRIVATE Catch2::Catch2WithMain)
30+
# When Catch is installed system wide we need to include a different
31+
# header, we need this define to use the correct one.
32+
target_compile_definitions(${exercise} PRIVATE EXERCISM_TEST_SUITE)
33+
else()
34+
# Build executable from sources and headers
35+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h test/tests-main.cpp)
36+
endif()
37+
38+
set_target_properties(${exercise} PROPERTIES
39+
CXX_STANDARD 17
40+
CXX_STANDARD_REQUIRED OFF
41+
CXX_EXTENSIONS OFF
42+
)
43+
44+
set(CMAKE_BUILD_TYPE Debug)
45+
46+
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(GNU|Clang)")
47+
set_target_properties(${exercise} PROPERTIES
48+
COMPILE_FLAGS "-Wall -Wextra -Wpedantic -Werror"
49+
)
50+
endif()
51+
52+
# Configure to run all the tests?
53+
if(${EXERCISM_RUN_ALL_TESTS})
54+
target_compile_definitions(${exercise} PRIVATE EXERCISM_RUN_ALL_TESTS)
55+
endif()
56+
57+
# Tell MSVC not to warn us about unchecked iterators in debug builds
58+
# Treat warnings as errors
59+
# Treat type conversion warnings C4244 and C4267 as level 4 warnings, i.e. ignore them in level 3
60+
if(${MSVC})
61+
set_target_properties(${exercise} PROPERTIES
62+
COMPILE_DEFINITIONS_DEBUG _SCL_SECURE_NO_WARNINGS
63+
COMPILE_FLAGS "/WX /w44244 /w44267")
64+
endif()
65+
66+
# Run the tests on every build
67+
add_custom_target(test_${exercise} ALL DEPENDS ${exercise} COMMAND ${exercise})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "affine_cipher.h"
2+
3+
namespace affine_cipher {
4+
5+
// TODO: add your solution here
6+
7+
} // namespace affine_cipher
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef AFFINE_CIPHER_H
2+
#define AFFINE_CIPHER_H
3+
4+
namespace affine_cipher {
5+
6+
// TODO: add your solution here
7+
8+
} // namespace affine_cipher
9+
10+
#endif // AFFINE_CIPHER_H

0 commit comments

Comments
 (0)