From d531a4630e13e93be12bf4c50ffaaf3bcdad748d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 24 Oct 2024 13:26:06 +1300 Subject: [PATCH] Add rail-fence-cipher exercise (#165) --- config.json | 8 +++ .../rail-fence-cipher/.docs/instructions.md | 57 +++++++++++++++++ .../rail-fence-cipher/.meta/Example.roc | 49 +++++++++++++++ .../rail-fence-cipher/.meta/config.json | 19 ++++++ .../rail-fence-cipher/.meta/template.j2 | 21 +++++++ .../rail-fence-cipher/.meta/tests.toml | 28 +++++++++ .../rail-fence-cipher/RailFenceCipher.roc | 9 +++ .../rail-fence-cipher-test.roc | 62 +++++++++++++++++++ 8 files changed, 253 insertions(+) create mode 100644 exercises/practice/rail-fence-cipher/.docs/instructions.md create mode 100644 exercises/practice/rail-fence-cipher/.meta/Example.roc create mode 100644 exercises/practice/rail-fence-cipher/.meta/config.json create mode 100644 exercises/practice/rail-fence-cipher/.meta/template.j2 create mode 100644 exercises/practice/rail-fence-cipher/.meta/tests.toml create mode 100644 exercises/practice/rail-fence-cipher/RailFenceCipher.roc create mode 100644 exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc diff --git a/config.json b/config.json index 052498c..957be6f 100644 --- a/config.json +++ b/config.json @@ -683,6 +683,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "rail-fence-cipher", + "name": "Rail Fence Cipher", + "uuid": "d4569bd5-b6f3-4f2e-bdc0-0c6122540800", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "acronym", "name": "Acronym", diff --git a/exercises/practice/rail-fence-cipher/.docs/instructions.md b/exercises/practice/rail-fence-cipher/.docs/instructions.md new file mode 100644 index 0000000..e311de6 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +Implement encoding and decoding for the rail fence cipher. + +The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded. +It was already used by the ancient Greeks. + +In the Rail Fence cipher, the message is written downwards on successive "rails" of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). +Finally the message is then read off in rows. + +For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out: + +```text +W . . . E . . . C . . . R . . . L . . . T . . . E +. E . R . D . S . O . E . E . F . E . A . O . C . +. . A . . . I . . . V . . . D . . . E . . . N . . +``` + +Then reads off: + +```text +WECRLTEERDSOEEFEAOCAIVDEN +``` + +To decrypt a message you take the zig-zag shape and fill the ciphertext along the rows. + +```text +? . . . ? . . . ? . . . ? . . . ? . . . ? . . . ? +. ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . +. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . . +``` + +The first row has seven spots that can be filled with "WECRLTE". + +```text +W . . . E . . . C . . . R . . . L . . . T . . . E +. ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . +. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . . +``` + +Now the 2nd row takes "ERDSOEEFEAOC". + +```text +W . . . E . . . C . . . R . . . L . . . T . . . E +. E . R . D . S . O . E . E . F . E . A . O . C . +. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . . +``` + +Leaving "AIVDEN" for the last row. + +```text +W . . . E . . . C . . . R . . . L . . . T . . . E +. E . R . D . S . O . E . E . F . E . A . O . C . +. . A . . . I . . . V . . . D . . . E . . . N . . +``` + +If you now read along the zig-zag shape you can read the original message. diff --git a/exercises/practice/rail-fence-cipher/.meta/Example.roc b/exercises/practice/rail-fence-cipher/.meta/Example.roc new file mode 100644 index 0000000..cf2fba5 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/Example.roc @@ -0,0 +1,49 @@ +module [encode, decode] + +encodedIndices : U64, U64 -> List U64 +encodedIndices = \len, rails -> + indices = List.range { start: At 0, end: Before len } + List.range { start: At 0, end: Before rails } + |> List.map \rail -> + period = 2 * (rails - 1) + indices + |> List.mapWithIndex \index, originalIndex -> + toRail = originalIndex % period + if toRail == rail || toRail == period - rail then + [index] + else + [] + |> List.join + |> List.join + +decodedIndices : U64, U64 -> List U64 +decodedIndices = \len, rails -> + encodedIndices len rails + |> List.mapWithIndex \encoded, decoded -> { encoded, decoded } + |> List.sortWith \{ encoded: encoded1 }, { encoded: encoded2 } -> + Num.compare encoded1 encoded2 + |> List.map .decoded + +encode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _ _] +encode = \message, rails -> + message |> reorderWith encodedIndices rails + +decode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _ _] +decode = \encrypted, rails -> + encrypted |> reorderWith decodedIndices rails + +reorderWith : Str, (U64, U64 -> List U64), U64 -> Result Str [ZeroRails, BadUtf8 _ _] +reorderWith = \message, getIndices, rails -> + if rails == 0 then + Err ZeroRails + else if rails == 1 then + Ok message + else + + chars = message |> Str.toUtf8 + result = + getIndices (List.len chars) rails + |> List.mapTry \index -> chars |> List.get index + when result is + Ok encryptedChars -> encryptedChars |> Str.fromUtf8 + Err OutOfBounds -> crash "Unreachable: indices cannot be out of bounds here" diff --git a/exercises/practice/rail-fence-cipher/.meta/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json new file mode 100644 index 0000000..a2f854d --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "RailFenceCipher.roc" + ], + "test": [ + "rail-fence-cipher-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Implement encoding and decoding for the rail fence cipher.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher" +} diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2 new file mode 100644 index 0000000..a739785 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/template.j2 @@ -0,0 +1,21 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [encode, decode] + +{% for supercase in cases %} +## +## {{ supercase["description"] }} +## + +{% for case in supercase["cases"] -%} +# {{ case["description"] }} +expect + message = {{ case["input"]["msg"] | to_roc }} + result = message |> {{ case["property"] | to_camel }} {{ case["input"]["rails"] | to_roc }} + expected = Ok {{ case["expected"] | to_roc }} + result == expected + +{% endfor %} +{% endfor %} diff --git a/exercises/practice/rail-fence-cipher/.meta/tests.toml b/exercises/practice/rail-fence-cipher/.meta/tests.toml new file mode 100644 index 0000000..dfc5e16 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/tests.toml @@ -0,0 +1,28 @@ +# 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. + +[46dc5c50-5538-401d-93a5-41102680d068] +description = "encode -> encode with two rails" + +[25691697-fbd8-4278-8c38-b84068b7bc29] +description = "encode -> encode with three rails" + +[384f0fea-1442-4f1a-a7c4-5cbc2044002c] +description = "encode -> encode with ending in the middle" + +[cd525b17-ec34-45ef-8f0e-4f27c24a7127] +description = "decode -> decode with three rails" + +[dd7b4a98-1a52-4e5c-9499-cbb117833507] +description = "decode -> decode with five rails" + +[93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3] +description = "decode -> decode with six rails" diff --git a/exercises/practice/rail-fence-cipher/RailFenceCipher.roc b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc new file mode 100644 index 0000000..e498182 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc @@ -0,0 +1,9 @@ +module [encode, decode] + +encode : Str, U64 -> Result Str _ +encode = \message, rails -> + crash "Please implement the 'encode' function" + +decode : Str, U64 -> Result Str _ +decode = \encrypted, rails -> + crash "Please implement the 'decode' function" diff --git a/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc new file mode 100644 index 0000000..806347d --- /dev/null +++ b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc @@ -0,0 +1,62 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json +# File last updated on 2024-10-23 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import RailFenceCipher exposing [encode, decode] + +## +## encode +## + +# encode with two rails +expect + message = "XOXOXOXOXOXOXOXOXO" + result = message |> encode 2 + expected = Ok "XXXXXXXXXOOOOOOOOO" + result == expected + +# encode with three rails +expect + message = "WEAREDISCOVEREDFLEEATONCE" + result = message |> encode 3 + expected = Ok "WECRLTEERDSOEEFEAOCAIVDEN" + result == expected + +# encode with ending in the middle +expect + message = "EXERCISES" + result = message |> encode 4 + expected = Ok "ESXIEECSR" + result == expected + +## +## decode +## + +# decode with three rails +expect + message = "TEITELHDVLSNHDTISEIIEA" + result = message |> decode 3 + expected = Ok "THEDEVILISINTHEDETAILS" + result == expected + +# decode with five rails +expect + message = "EIEXMSMESAORIWSCE" + result = message |> decode 5 + expected = Ok "EXERCISMISAWESOME" + result == expected + +# decode with six rails +expect + message = "133714114238148966225439541018335470986172518171757571896261" + result = message |> decode 6 + expected = Ok "112358132134558914423337761098715972584418167651094617711286" + result == expected +