diff --git a/config.json b/config.json index a7c89b2..05266f1 100644 --- a/config.json +++ b/config.json @@ -929,6 +929,14 @@ "nested_classes", "reactive_programming" ] + }, + { + "slug": "simple-cipher", + "name": "Simple Cipher", + "uuid": "3ce7b91b-330f-4fbb-8e9a-3638bd90281b", + "practices": [], + "prerequisites": [], + "difficulty": 5 } ] }, diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md new file mode 100644 index 0000000..afd0b57 --- /dev/null +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -0,0 +1,40 @@ +# Instructions + +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. + +## Cipher terminology + +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. + +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) + +## Encoding details + +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. + +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. + +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. + +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. + +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. + +## Random keys + +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. + +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json new file mode 100644 index 0000000..6a9e7d2 --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Narkunan" + ], + "files": { + "solution": [ + "source/simple_cipher.d" + ], + "test": [ + "source/simple_cipher.d" + ], + "example": [ + "example/simple_cipher.d" + ] + }, + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", + "source": "Substitution Cipher at Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" +} diff --git a/exercises/practice/simple-cipher/.meta/tests.toml b/exercises/practice/simple-cipher/.meta/tests.toml new file mode 100644 index 0000000..77e6571 --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b8bdfbe1-bea3-41bb-a999-b41403f2b15d] +description = "Random key cipher -> Can encode" + +[3dff7f36-75db-46b4-ab70-644b3f38b81c] +description = "Random key cipher -> Can decode" + +[8143c684-6df6-46ba-bd1f-dea8fcb5d265] +description = "Random key cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[defc0050-e87d-4840-85e4-51a1ab9dd6aa] +description = "Random key cipher -> Key is made only of lowercase letters" + +[565e5158-5b3b-41dd-b99d-33b9f413c39f] +description = "Substitution cipher -> Can encode" + +[d44e4f6a-b8af-4e90-9d08-fd407e31e67b] +description = "Substitution cipher -> Can decode" + +[70a16473-7339-43df-902d-93408c69e9d1] +description = "Substitution cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[69a1458b-92a6-433a-a02d-7beac3ea91f9] +description = "Substitution cipher -> Can double shift encode" + +[21d207c1-98de-40aa-994f-86197ae230fb] +description = "Substitution cipher -> Can wrap on encode" + +[a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3] +description = "Substitution cipher -> Can wrap on decode" + +[e31c9b8c-8eb6-45c9-a4b5-8344a36b9641] +description = "Substitution cipher -> Can encode messages longer than the key" + +[93cfaae0-17da-4627-9a04-d6d1e1be52e3] +description = "Substitution cipher -> Can decode messages longer than the key" diff --git a/exercises/practice/simple-cipher/dub.sdl b/exercises/practice/simple-cipher/dub.sdl new file mode 100644 index 0000000..d673cd3 --- /dev/null +++ b/exercises/practice/simple-cipher/dub.sdl @@ -0,0 +1,2 @@ +name "simple-cipher" +buildRequirements "disallowDeprecations" diff --git a/exercises/practice/simple-cipher/example/simple_cipher.d b/exercises/practice/simple-cipher/example/simple_cipher.d new file mode 100644 index 0000000..0a59062 --- /dev/null +++ b/exercises/practice/simple-cipher/example/simple_cipher.d @@ -0,0 +1,53 @@ +import std.stdio; +import std.random; +import std.string; +import std.algorithm; +import std.array; +import std.range; +import std.exception; + +class VigenereCipher { + private string key; + + this(string key = "") { + this.key = key; + } + + // Encode plaintext + string encode(string plaintext) { + return transformText(plaintext, true); + } + + // Decode ciphertext + string decode(string ciphertext) { + return transformText(ciphertext, false); + } + + // Core logic + private string transformText(string text, bool encodeMode) { + string result; + size_t keyIndex = 0; + + foreach (char c; text) { + if (c >= 'a' && c <= 'z') { + int shift = key[keyIndex % key.length] - 'a'; + + if (!encodeMode) { + shift = -shift; + } + + int newChar = (c - 'a' + shift) % 26; + if (newChar < 0) newChar += 26; + + result ~= cast(char)('a' + newChar); + keyIndex++; + } else { + // Keep non-lowercase chars unchanged + result ~= c; + } + } + + return result; + } + +} \ No newline at end of file diff --git a/exercises/practice/simple-cipher/source/simple_cipher.d b/exercises/practice/simple-cipher/source/simple_cipher.d new file mode 100644 index 0000000..277f6a5 --- /dev/null +++ b/exercises/practice/simple-cipher/source/simple_cipher.d @@ -0,0 +1,70 @@ +import std.stdio; +import std.random; +import std.string; +import std.algorithm; +import std.array; +import std.range; +import std.exception; + +class VigenereCipher { + private string key; + + this(string key = "") { + this.key = key; + } + + // Encode plaintext + string encode(string plaintext) { + // implement this function + } + + // Decode ciphertext + string decode(string ciphertext) { + // implement this function + } + +} + +unittest { + // Basic encoding test + auto cipher = new VigenereCipher("abcd"); + assert(cipher.encode("hello") == "hfnoo"); +} + +unittest { + // Decode should reverse encode + auto cipher = new VigenereCipher("abcd"); + string original = "hello"; + auto encoded = cipher.encode(original); + auto decoded = cipher.decode(encoded); + + assert(decoded == original); +} + +unittest { + // Single character key (Caesar cipher behavior) + auto cipher = new VigenereCipher("d"); // shift = 3 + assert(cipher.encode("abc") == "def"); + assert(cipher.decode("def") == "abc"); +} + +unittest { + // Key = "a" should not change text + auto cipher = new VigenereCipher("a"); + assert(cipher.encode("hello") == "hello"); + assert(cipher.decode("hello") == "hello"); +} + +unittest { + // Key wrapping + auto cipher = new VigenereCipher("ab"); + // shifts: 0,1,0,1,... + assert(cipher.encode("aaaa") == "abab"); +} + +unittest { + // Non-lowercase characters should remain unchanged + auto cipher = new VigenereCipher("abc"); + assert(cipher.encode("hello world!") == "hfnlp yosnd!"); +} +