Skip to content

Commit ba62aa1

Browse files
authored
Add simple-cipher (#146)
1 parent 0033ca8 commit ba62aa1

9 files changed

Lines changed: 287 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,14 @@
746746
"prerequisites": [],
747747
"difficulty": 5
748748
},
749+
{
750+
"slug": "simple-cipher",
751+
"name": "Simple Cipher",
752+
"uuid": "f55d8d06-21cf-4ac4-a6cc-61ed90ff3588",
753+
"practices": [],
754+
"prerequisites": [],
755+
"difficulty": 5
756+
},
749757
{
750758
"slug": "simple-linked-list",
751759
"name": "Simple Linked List",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Instructions
2+
3+
Create an implementation of the [Vigenère cipher][wiki].
4+
The Vigenère cipher is a simple substitution cipher.
5+
6+
## Cipher terminology
7+
8+
A cipher is an algorithm used to encrypt, or encode, a string.
9+
The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_.
10+
Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_.
11+
12+
In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_.
13+
(Note, it is possible for replacement letter to be the same as the original letter.)
14+
15+
## Encoding details
16+
17+
In this cipher, the key is a series of lowercase letters, such as `"abcd"`.
18+
Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key.
19+
An `"a"` in the key means a shift of 0 (that is, no shift).
20+
A `"b"` in the key means a shift of 1.
21+
A `"c"` in the key means a shift of 2, and so on.
22+
23+
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.
24+
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.
25+
26+
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).
27+
For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`.
28+
29+
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.
30+
31+
Usually the key is more complicated than that, though!
32+
If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3.
33+
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.
34+
Applying those shifts to the letters of `"hello"` we get `"hfnoo"`.
35+
36+
## Random keys
37+
38+
If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet.
39+
40+
[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"simple_cipher.moon"
8+
],
9+
"test": [
10+
"simple_cipher_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Implement the Vigenère cipher, a simple substitution cipher.",
17+
"source": "Substitution Cipher at Wikipedia",
18+
"source_url": "https://en.wikipedia.org/wiki/Substitution_cipher"
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
KEY_CHARS = [c for c in 'abcdefghijklmnopqrstuvwxyz'\gmatch '.']
2+
A = string.byte 'a'
3+
INDEX = {c, string.byte(c) - A for c in *KEY_CHARS}
4+
5+
randomKey = ->
6+
table.concat [KEY_CHARS[math.random #KEY_CHARS] for _ = 1, 100]
7+
8+
class SimpleCipher
9+
new: (key) =>
10+
@Key = key or randomKey!
11+
12+
key: => @Key
13+
14+
encipher: (text, direction) =>
15+
while #@Key < #text
16+
@Key ..= @Key
17+
18+
idx = 0
19+
text\gsub '.', (char) ->
20+
idx += 1
21+
t = INDEX[char]
22+
k = INDEX[@Key\sub idx, idx]
23+
i = (t + direction * k) % #KEY_CHARS + 1
24+
KEY_CHARS[i]
25+
26+
encode: (text) => @encipher text, 1
27+
decode: (text) => @encipher text, -1
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
module_name: 'SimpleCipher',
3+
4+
generate_test: (case, level) ->
5+
local lines
6+
switch case.uuid
7+
when "b8bdfbe1-bea3-41bb-a999-b41403f2b15d"
8+
lines = {
9+
"cipher = SimpleCipher!"
10+
"plaintext = #{quote case.input.plaintext}"
11+
"encoded = cipher\\encode plaintext"
12+
"expected = cipher\\key!\\sub(1, #plaintext)"
13+
"assert.are.equal expected, encoded"
14+
}
15+
when "3dff7f36-75db-46b4-ab70-644b3f38b81c"
16+
lines = {
17+
"cipher = SimpleCipher!"
18+
"expected = #{quote case.expected}"
19+
"ciphertext = cipher\\key!\\sub(1, #expected)"
20+
"decoded = cipher\\decode ciphertext"
21+
"assert.are.equal expected, decoded"
22+
}
23+
when "8143c684-6df6-46ba-bd1f-dea8fcb5d265"
24+
lines = {
25+
"cipher = SimpleCipher!"
26+
"plaintext = #{quote case.input.plaintext}"
27+
"ciphertext = cipher\\encode plaintext"
28+
"decoded = cipher\\decode ciphertext"
29+
"assert.are.equal plaintext, decoded"
30+
}
31+
when "defc0050-e87d-4840-85e4-51a1ab9dd6aa"
32+
lines = {
33+
"cipher = SimpleCipher!"
34+
"key = cipher\\key!"
35+
"assert.is.truthy key\\match('^[a-z]+$')"
36+
}
37+
when "70a16473-7339-43df-902d-93408c69e9d1"
38+
lines = {
39+
"cipher = SimpleCipher #{quote case.input.key}"
40+
"ciphertext = cipher\\encode #{quote case.input.plaintext}"
41+
"result = cipher\\decode ciphertext"
42+
"assert.are.equal #{quote case.expected}, result"
43+
}
44+
else
45+
switch case.property
46+
when "encode"
47+
lines = {
48+
"cipher = SimpleCipher #{quote case.input.key}"
49+
"result = cipher\\encode #{quote case.input.plaintext}"
50+
"assert.are.equal #{quote case.expected}, result"
51+
}
52+
when "decode"
53+
lines = {
54+
"cipher = SimpleCipher #{quote case.input.key}"
55+
"result = cipher\\decode #{quote case.input.ciphertext}"
56+
"assert.are.equal #{quote case.expected}, result"
57+
}
58+
table.concat [indent line, level for line in *lines], '\n'
59+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
[b8bdfbe1-bea3-41bb-a999-b41403f2b15d]
13+
description = "Random key cipher -> Can encode"
14+
15+
[3dff7f36-75db-46b4-ab70-644b3f38b81c]
16+
description = "Random key cipher -> Can decode"
17+
18+
[8143c684-6df6-46ba-bd1f-dea8fcb5d265]
19+
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"
20+
21+
[defc0050-e87d-4840-85e4-51a1ab9dd6aa]
22+
description = "Random key cipher -> Key is made only of lowercase letters"
23+
24+
[565e5158-5b3b-41dd-b99d-33b9f413c39f]
25+
description = "Substitution cipher -> Can encode"
26+
27+
[d44e4f6a-b8af-4e90-9d08-fd407e31e67b]
28+
description = "Substitution cipher -> Can decode"
29+
30+
[70a16473-7339-43df-902d-93408c69e9d1]
31+
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"
32+
33+
[69a1458b-92a6-433a-a02d-7beac3ea91f9]
34+
description = "Substitution cipher -> Can double shift encode"
35+
36+
[21d207c1-98de-40aa-994f-86197ae230fb]
37+
description = "Substitution cipher -> Can wrap on encode"
38+
39+
[a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3]
40+
description = "Substitution cipher -> Can wrap on decode"
41+
42+
[e31c9b8c-8eb6-45c9-a4b5-8344a36b9641]
43+
description = "Substitution cipher -> Can encode messages longer than the key"
44+
45+
[93cfaae0-17da-4627-9a04-d6d1e1be52e3]
46+
description = "Substitution cipher -> Can decode messages longer than the key"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class SimpleCipher
2+
new: () =>
3+
error 'Implement the constructor'
4+
5+
key: =>
6+
error 'Implement the "key" method'
7+
8+
encode: (plaintext) =>
9+
error 'Implement the "encode" method'
10+
11+
decode: (ciphertext) =>
12+
error 'Implement the "decode" method'
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
SimpleCipher = require 'simple_cipher'
2+
3+
describe 'simple-cipher', ->
4+
describe 'Random key cipher', ->
5+
it 'Can encode', ->
6+
cipher = SimpleCipher!
7+
plaintext = 'aaaaaaaaaa'
8+
encoded = cipher\encode plaintext
9+
expected = cipher\key!\sub(1, #plaintext)
10+
assert.are.equal expected, encoded
11+
12+
pending 'Can decode', ->
13+
cipher = SimpleCipher!
14+
expected = 'aaaaaaaaaa'
15+
ciphertext = cipher\key!\sub(1, #expected)
16+
decoded = cipher\decode ciphertext
17+
assert.are.equal expected, decoded
18+
19+
pending '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', ->
20+
cipher = SimpleCipher!
21+
plaintext = 'abcdefghij'
22+
ciphertext = cipher\encode plaintext
23+
decoded = cipher\decode ciphertext
24+
assert.are.equal plaintext, decoded
25+
26+
pending 'Key is made only of lowercase letters', ->
27+
cipher = SimpleCipher!
28+
key = cipher\key!
29+
assert.is.truthy key\match('^[a-z]+$')
30+
31+
describe 'Substitution cipher', ->
32+
pending 'Can encode', ->
33+
cipher = SimpleCipher 'abcdefghij'
34+
result = cipher\encode 'aaaaaaaaaa'
35+
assert.are.equal 'abcdefghij', result
36+
37+
pending 'Can decode', ->
38+
cipher = SimpleCipher 'abcdefghij'
39+
result = cipher\decode 'abcdefghij'
40+
assert.are.equal 'aaaaaaaaaa', result
41+
42+
pending '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', ->
43+
cipher = SimpleCipher 'abcdefghij'
44+
ciphertext = cipher\encode 'abcdefghij'
45+
result = cipher\decode ciphertext
46+
assert.are.equal 'abcdefghij', result
47+
48+
pending 'Can double shift encode', ->
49+
cipher = SimpleCipher 'iamapandabear'
50+
result = cipher\encode 'iamapandabear'
51+
assert.are.equal 'qayaeaagaciai', result
52+
53+
pending 'Can wrap on encode', ->
54+
cipher = SimpleCipher 'abcdefghij'
55+
result = cipher\encode 'zzzzzzzzzz'
56+
assert.are.equal 'zabcdefghi', result
57+
58+
pending 'Can wrap on decode', ->
59+
cipher = SimpleCipher 'abcdefghij'
60+
result = cipher\decode 'zabcdefghi'
61+
assert.are.equal 'zzzzzzzzzz', result
62+
63+
pending 'Can encode messages longer than the key', ->
64+
cipher = SimpleCipher 'abc'
65+
result = cipher\encode 'iamapandabear'
66+
assert.are.equal 'iboaqcnecbfcr', result
67+
68+
pending 'Can decode messages longer than the key', ->
69+
cipher = SimpleCipher 'abc'
70+
result = cipher\decode 'iboaqcnecbfcr'
71+
assert.are.equal 'iamapandabear', result

0 commit comments

Comments
 (0)