diff --git a/config.json b/config.json index 6a58041..e5082f8 100644 --- a/config.json +++ b/config.json @@ -826,6 +826,14 @@ "prerequisites": [], "difficulty": 6 }, + { + "slug": "rail-fence-cipher", + "name": "Rail Fence Cipher", + "uuid": "0dfe9492-7349-456a-a4fd-afde1383c393", + "practices": [], + "prerequisites": [], + "difficulty": 6 + }, { "slug": "say", "name": "Say", diff --git a/exercises/practice/rail-fence-cipher/.busted b/exercises/practice/rail-fence-cipher/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} 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/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json new file mode 100644 index 0000000..a6c6ef1 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "rail_fence_cipher.moon" + ], + "test": [ + "rail_fence_cipher_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "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/example.moon b/exercises/practice/rail-fence-cipher/.meta/example.moon new file mode 100644 index 0000000..f17cba4 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/example.moon @@ -0,0 +1,39 @@ +rail_iter = (n) -> + coroutine.wrap -> + while true + coroutine.yield i for i = 1, n-1 + coroutine.yield i for i = n, 2, -1 + +{ + encode: (n, plaintext) -> + rails = [{} for _ = 1, n] + next_rail = rail_iter n + for char in plaintext\gmatch '.' + r = next_rail! + table.insert rails[r], char + + table.concat [table.concat rail for rail in *rails] + + decode: (n, ciphertext) -> + len = #ciphertext + + -- find the length of each rail + cycle_length = 2*(n - 1) + cycles = len // cycle_length + lengths = [cycles * ((i == 1 or i == n) and 1 or 2) for i = 1, n] + -- account for the last partial cycle + next_rail = rail_iter n + for i = 1, len - cycles * cycle_length + lengths[next_rail!] += 1 + + -- extract the rails from the ciphertext + rails = {} + start = 1 + for r = 1, n + chunk = ciphertext\sub start, start + lengths[r] - 1 + rails[r] = [c for c in chunk\gmatch '.'] + start += lengths[r] + + next_rail = rail_iter n + table.concat [table.remove rails[next_rail!], 1 for _ = 1, len] +} diff --git a/exercises/practice/rail-fence-cipher/.meta/spec_generator.moon b/exercises/practice/rail-fence-cipher/.meta/spec_generator.moon new file mode 100644 index 0000000..ea760a6 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/spec_generator.moon @@ -0,0 +1,11 @@ +{ + module_name: 'RailFenceCipher', + + generate_test: (case, level) -> + lines = { + "result = RailFenceCipher.#{case.property} #{case.input.rails}, #{quote case.input.msg}", + "expected = #{quote case.expected}", + "assert.are.equal expected, result" + } + table.concat [indent line, level for line in *lines], '\n' +} 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/rail_fence_cipher.moon b/exercises/practice/rail-fence-cipher/rail_fence_cipher.moon new file mode 100644 index 0000000..9e3bd01 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher.moon @@ -0,0 +1,7 @@ +{ + encode: (n, plaintext) -> + error 'Implement me' + + decode: (n, ciphertext) -> + error 'Implement me' +} diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_spec.moon b/exercises/practice/rail-fence-cipher/rail_fence_cipher_spec.moon new file mode 100644 index 0000000..39e36c1 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_spec.moon @@ -0,0 +1,34 @@ +RailFenceCipher = require 'rail_fence_cipher' + +describe 'rail-fence-cipher', -> + describe 'encode', -> + it 'encode with two rails', -> + result = RailFenceCipher.encode 2, 'XOXOXOXOXOXOXOXOXO' + expected = 'XXXXXXXXXOOOOOOOOO' + assert.are.equal expected, result + + pending 'encode with three rails', -> + result = RailFenceCipher.encode 3, 'WEAREDISCOVEREDFLEEATONCE' + expected = 'WECRLTEERDSOEEFEAOCAIVDEN' + assert.are.equal expected, result + + pending 'encode with ending in the middle', -> + result = RailFenceCipher.encode 4, 'EXERCISES' + expected = 'ESXIEECSR' + assert.are.equal expected, result + + describe 'decode', -> + pending 'decode with three rails', -> + result = RailFenceCipher.decode 3, 'TEITELHDVLSNHDTISEIIEA' + expected = 'THEDEVILISINTHEDETAILS' + assert.are.equal expected, result + + pending 'decode with five rails', -> + result = RailFenceCipher.decode 5, 'EIEXMSMESAORIWSCE' + expected = 'EXERCISMISAWESOME' + assert.are.equal expected, result + + pending 'decode with six rails', -> + result = RailFenceCipher.decode 6, '133714114238148966225439541018335470986172518171757571896261' + expected = '112358132134558914423337761098715972584418167651094617711286' + assert.are.equal expected, result