From aab7be62cf6e7ec5e6df158f18ca14508128ee5a Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:52:41 +0530 Subject: [PATCH 1/8] Added config to include simple cipher --- config.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.json b/config.json index a7c89b2..5c26098 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": 1 } ] }, From a5a11de6e50fadd9692760d1b15f98a387ecbd03 Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:53:13 +0530 Subject: [PATCH 2/8] Added instructions.md --- .../simple-cipher/.docs/instructions.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 exercises/practice/simple-cipher/.docs/instructions.md 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 From 93bdf96e8620615d92dcca730540c34e7b50c04a Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:53:40 +0530 Subject: [PATCH 3/8] Added meta config for simple-cipher --- .../practice/simple-cipher/.meta/config.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 exercises/practice/simple-cipher/.meta/config.json diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json new file mode 100644 index 0000000..235b01f --- /dev/null +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [], + "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" +} From 52f14c922acf8d7fe857100378036a91195109ef Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:53:56 +0530 Subject: [PATCH 4/8] Added tests.toml --- .../practice/simple-cipher/.meta/tests.toml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 exercises/practice/simple-cipher/.meta/tests.toml 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" From 219e272c84bd3e033e362846c678cae4dfe781c1 Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:54:36 +0530 Subject: [PATCH 5/8] Added simple_cipher example and actual program --- .../simple-cipher/example/simple_cipher.d | 65 ++++++++ .../simple-cipher/source/simple_cipher.d | 140 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 exercises/practice/simple-cipher/example/simple_cipher.d create mode 100644 exercises/practice/simple-cipher/source/simple_cipher.d 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..afbe2f0 --- /dev/null +++ b/exercises/practice/simple-cipher/example/simple_cipher.d @@ -0,0 +1,65 @@ +module simple_cipher; + +import std.array; +import std.algorithm; +import std.range; +import std.random; +import std.exception; +import std.array : appender; + +struct Cipher { + private string key; + + this(string key) { + enforce(key.length > 0, "Key cannot be empty"); + enforce(key.all!(c => c >= 'a' && c <= 'z'), + "Key must be lowercase letters"); + this.key = key; + } + + static Cipher withRandomKey() { + Cipher c; + c.key = generateKey(100); + return c; + } + + string encode(string text) const { + return transform(text, true); + } + + string decode(string text) const { + return transform(text, false); + } + +private: + string transform(string text, bool encodeMode) const { + auto result = appender!string(); + size_t keyIndex = 0; + + foreach (c; text) { + if (c >= 'a' && c <= 'z') { + int shift = key[keyIndex % key.length] - 'a'; + if (!encodeMode) shift = -shift; + + int val = (c - 'a' + shift) % 26; + if (val < 0) val += 26; + + result.put(cast(char)('a' + val)); + keyIndex++; + } else { + result.put(c); + } + } + + return result.data; + } + + static string generateKey(size_t length) { + auto rng = Random(unpredictableSeed); + return iota(length) + .map!(_ => cast(char)('a' + uniform(0, 26, rng))) + .array; + } +} + +void main() {} \ 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..fea4a9e --- /dev/null +++ b/exercises/practice/simple-cipher/source/simple_cipher.d @@ -0,0 +1,140 @@ +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 = "") { + if (key.length == 0) { + this.key = generateRandomKey(100); + } else { + validateKey(key); + this.key = key; + } + } + + // Generate random key of given length + private string generateRandomKey(size_t length) { + auto rng = Random(unpredictableSeed); + return array(map!(i => cast(char)('a' + uniform(0, 26, rng)))(iota(length))); + } + + // Ensure key is lowercase letters only + private void validateKey(string key) { + enforce!Exception(key.length > 0, "Key cannot be empty"); + enforce!Exception(all!((c) => c >= 'a' && c <= 'z')(key), + "Key must contain only lowercase letters"); + } + + // 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; + } + + string getKey() { + return key; + } +} + +void main() { + auto cipher = new VigenereCipher("abcd"); + + string plaintext = "hello"; + auto encoded = cipher.encode(plaintext); + auto decoded = cipher.decode(encoded); + + writeln("Key: ", cipher.getKey()); + writeln("Plaintext: ", plaintext); + writeln("Encoded: ", encoded); + writeln("Decoded: ", decoded); + + // Random key example + auto randomCipher = new VigenereCipher(); + writeln("\nRandom Key: ", randomCipher.getKey()); +} + +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!"); +} + +unittest { + // Random key generation (length check) + auto cipher = new VigenereCipher(); + assert(cipher.getKey().length >= 100); +} From 18a924f688fb9cf6f176b79d4523b94ec9e3e331 Mon Sep 17 00:00:00 2001 From: narkunan Date: Sat, 11 Apr 2026 18:56:28 +0530 Subject: [PATCH 6/8] Added author detail and dub.sdl --- exercises/practice/simple-cipher/.meta/config.json | 4 +++- exercises/practice/simple-cipher/dub.sdl | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/simple-cipher/dub.sdl diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 235b01f..6a9e7d2 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -1,5 +1,7 @@ { - "authors": [], + "authors": [ + "Narkunan" + ], "files": { "solution": [ "source/simple_cipher.d" 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" From ec0d3f915a6e5a5ed982b4dfac3aa1f7d7cf13b9 Mon Sep 17 00:00:00 2001 From: narkunan Date: Sun, 12 Apr 2026 19:41:11 +0530 Subject: [PATCH 7/8] Updated task difficulty --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 5c26098..05266f1 100644 --- a/config.json +++ b/config.json @@ -936,7 +936,7 @@ "uuid": "3ce7b91b-330f-4fbb-8e9a-3638bd90281b", "practices": [], "prerequisites": [], - "difficulty": 1 + "difficulty": 5 } ] }, From 361f899dd2667867a91c6c76f137512362c12d5b Mon Sep 17 00:00:00 2001 From: narkunan Date: Sun, 12 Apr 2026 19:41:32 +0530 Subject: [PATCH 8/8] Updated simple_cipher content in example and source --- .../simple-cipher/example/simple_cipher.d | 68 +++++++---------- .../simple-cipher/source/simple_cipher.d | 76 +------------------ 2 files changed, 31 insertions(+), 113 deletions(-) diff --git a/exercises/practice/simple-cipher/example/simple_cipher.d b/exercises/practice/simple-cipher/example/simple_cipher.d index afbe2f0..0a59062 100644 --- a/exercises/practice/simple-cipher/example/simple_cipher.d +++ b/exercises/practice/simple-cipher/example/simple_cipher.d @@ -1,65 +1,53 @@ -module simple_cipher; - -import std.array; +import std.stdio; +import std.random; +import std.string; import std.algorithm; +import std.array; import std.range; -import std.random; import std.exception; -import std.array : appender; -struct Cipher { +class VigenereCipher { private string key; - this(string key) { - enforce(key.length > 0, "Key cannot be empty"); - enforce(key.all!(c => c >= 'a' && c <= 'z'), - "Key must be lowercase letters"); - this.key = key; + this(string key = "") { + this.key = key; } - static Cipher withRandomKey() { - Cipher c; - c.key = generateKey(100); - return c; + // Encode plaintext + string encode(string plaintext) { + return transformText(plaintext, true); } - string encode(string text) const { - return transform(text, true); + // Decode ciphertext + string decode(string ciphertext) { + return transformText(ciphertext, false); } - string decode(string text) const { - return transform(text, false); - } - -private: - string transform(string text, bool encodeMode) const { - auto result = appender!string(); + // Core logic + private string transformText(string text, bool encodeMode) { + string result; size_t keyIndex = 0; - foreach (c; text) { + foreach (char c; text) { if (c >= 'a' && c <= 'z') { int shift = key[keyIndex % key.length] - 'a'; - if (!encodeMode) shift = -shift; - int val = (c - 'a' + shift) % 26; - if (val < 0) val += 26; + if (!encodeMode) { + shift = -shift; + } + + int newChar = (c - 'a' + shift) % 26; + if (newChar < 0) newChar += 26; - result.put(cast(char)('a' + val)); + result ~= cast(char)('a' + newChar); keyIndex++; } else { - result.put(c); + // Keep non-lowercase chars unchanged + result ~= c; } } - return result.data; - } - - static string generateKey(size_t length) { - auto rng = Random(unpredictableSeed); - return iota(length) - .map!(_ => cast(char)('a' + uniform(0, 26, rng))) - .array; + return result; } -} -void main() {} \ No newline at end of file +} \ 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 index fea4a9e..277f6a5 100644 --- a/exercises/practice/simple-cipher/source/simple_cipher.d +++ b/exercises/practice/simple-cipher/source/simple_cipher.d @@ -10,84 +10,19 @@ class VigenereCipher { private string key; this(string key = "") { - if (key.length == 0) { - this.key = generateRandomKey(100); - } else { - validateKey(key); - this.key = key; - } - } - - // Generate random key of given length - private string generateRandomKey(size_t length) { - auto rng = Random(unpredictableSeed); - return array(map!(i => cast(char)('a' + uniform(0, 26, rng)))(iota(length))); - } - - // Ensure key is lowercase letters only - private void validateKey(string key) { - enforce!Exception(key.length > 0, "Key cannot be empty"); - enforce!Exception(all!((c) => c >= 'a' && c <= 'z')(key), - "Key must contain only lowercase letters"); + this.key = key; } // Encode plaintext string encode(string plaintext) { - return transformText(plaintext, true); + // implement this function } // 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; + // implement this function } - string getKey() { - return key; - } -} - -void main() { - auto cipher = new VigenereCipher("abcd"); - - string plaintext = "hello"; - auto encoded = cipher.encode(plaintext); - auto decoded = cipher.decode(encoded); - - writeln("Key: ", cipher.getKey()); - writeln("Plaintext: ", plaintext); - writeln("Encoded: ", encoded); - writeln("Decoded: ", decoded); - - // Random key example - auto randomCipher = new VigenereCipher(); - writeln("\nRandom Key: ", randomCipher.getKey()); } unittest { @@ -133,8 +68,3 @@ unittest { assert(cipher.encode("hello world!") == "hfnlp yosnd!"); } -unittest { - // Random key generation (length check) - auto cipher = new VigenereCipher(); - assert(cipher.getKey().length >= 100); -}