Skip to content

Commit 6492d8c

Browse files
Add rail fence cipher exercise (#865)
Co-authored-by: meatball <[email protected]>
1 parent ddaf487 commit 6492d8c

File tree

9 files changed

+241
-0
lines changed

9 files changed

+241
-0
lines changed

config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,19 @@
11811181
"transforming"
11821182
]
11831183
},
1184+
{
1185+
"slug": "rail-fence-cipher",
1186+
"name": "Rail Fence Cipher",
1187+
"uuid": "f78800c1-8e89-4aac-83a4-3988e276225f",
1188+
"practices": [],
1189+
"prerequisites": [],
1190+
"difficulty": 5,
1191+
"topics": [
1192+
"algorithms",
1193+
"strings",
1194+
"arrays"
1195+
]
1196+
},
11841197
{
11851198
"slug": "spiral-matrix",
11861199
"name": "Spiral Matrix",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Instructions
2+
3+
Implement encoding and decoding for the rail fence cipher.
4+
5+
The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded.
6+
It was already used by the ancient Greeks.
7+
8+
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).
9+
Finally the message is then read off in rows.
10+
11+
For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out:
12+
13+
```text
14+
W . . . E . . . C . . . R . . . L . . . T . . . E
15+
. E . R . D . S . O . E . E . F . E . A . O . C .
16+
. . A . . . I . . . V . . . D . . . E . . . N . .
17+
```
18+
19+
Then reads off:
20+
21+
```text
22+
WECRLTEERDSOEEFEAOCAIVDEN
23+
```
24+
25+
To decrypt a message you take the zig-zag shape and fill the ciphertext along the rows.
26+
27+
```text
28+
? . . . ? . . . ? . . . ? . . . ? . . . ? . . . ?
29+
. ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? .
30+
. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . .
31+
```
32+
33+
The first row has seven spots that can be filled with "WECRLTE".
34+
35+
```text
36+
W . . . E . . . C . . . R . . . L . . . T . . . E
37+
. ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? . ? .
38+
. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . .
39+
```
40+
41+
Now the 2nd row takes "ERDSOEEFEAOC".
42+
43+
```text
44+
W . . . E . . . C . . . R . . . L . . . T . . . E
45+
. E . R . D . S . O . E . E . F . E . A . O . C .
46+
. . ? . . . ? . . . ? . . . ? . . . ? . . . ? . .
47+
```
48+
49+
Leaving "AIVDEN" for the last row.
50+
51+
```text
52+
W . . . E . . . C . . . R . . . L . . . T . . . E
53+
. E . R . D . S . O . E . E . F . E . A . O . C .
54+
. . A . . . I . . . V . . . D . . . E . . . N . .
55+
```
56+
57+
If you now read along the zig-zag shape you can read the original message.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
func encode(_ message: String, rails: Int) -> String {
3+
encodeDecode(message, rails: rails, mode: .encode)
4+
}
5+
6+
func decode(_ message: String, rails: Int) -> String {
7+
encodeDecode(message, rails: rails, mode: .decode)
8+
}
9+
10+
fileprivate enum Mode {
11+
case encode
12+
case decode
13+
}
14+
15+
fileprivate func encodeDecode(_ message: String, rails: Int, mode: Mode) -> String {
16+
var output = Array(repeating: Character(" "), count: message.count)
17+
let maxStep = 2 * (rails - 1)
18+
var posCipher = 0
19+
let chars = Array(message)
20+
21+
for rail in 0..<rails {
22+
var step = (rail == 0) ? maxStep : 2 * rail
23+
var posText = rail
24+
25+
while posText < chars.count {
26+
if mode == .encode {
27+
output[posCipher] = chars[posText]
28+
} else {
29+
output[posText] = chars[posCipher]
30+
}
31+
32+
posCipher += 1
33+
step = (step == maxStep) ? maxStep : maxStep - step
34+
posText += step
35+
}
36+
}
37+
38+
return String(output)
39+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"Sencudra"
4+
],
5+
"files": {
6+
"solution": [
7+
"Sources/RailFenceCipher/RailFenceCipher.swift"
8+
],
9+
"test": [
10+
"Tests/RailFenceCipherTests/RailFenceCipherTests.swift"
11+
],
12+
"example": [
13+
".meta/Sources/RailFenceCipher/RailFenceCipherExample.swift"
14+
]
15+
},
16+
"blurb": "Implement encoding and decoding for the rail fence cipher.",
17+
"source": "Wikipedia",
18+
"source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher"
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Testing
2+
import Foundation
3+
@testable import {{exercise | camelCase}}
4+
5+
let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false
6+
7+
@Suite struct {{exercise | camelCase}}Tests {
8+
{% outer: for case in cases %}
9+
{% for subCase in case.cases %}
10+
{% if forloop.outer.first and forloop.first %}
11+
@Test("{{subCase.description}}")
12+
{% else -%}
13+
@Test("{{subCase.description}}", .enabled(if: RUNALL))
14+
{% endif -%}
15+
func test{{subCase.description | camelCase}}() {
16+
#expect({{subCase.property}}("{{subCase.input.msg}}", rails: {{subCase.input.rails}}) == "{{subCase.expected}}")
17+
}
18+
{% endfor %}
19+
{% endfor %}
20+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
[46dc5c50-5538-401d-93a5-41102680d068]
13+
description = "encode -> encode with two rails"
14+
15+
[25691697-fbd8-4278-8c38-b84068b7bc29]
16+
description = "encode -> encode with three rails"
17+
18+
[384f0fea-1442-4f1a-a7c4-5cbc2044002c]
19+
description = "encode -> encode with ending in the middle"
20+
21+
[cd525b17-ec34-45ef-8f0e-4f27c24a7127]
22+
description = "decode -> decode with three rails"
23+
24+
[dd7b4a98-1a52-4e5c-9499-cbb117833507]
25+
description = "decode -> decode with five rails"
26+
27+
[93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3]
28+
description = "decode -> decode with six rails"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// swift-tools-version: 6.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "RailFenceCipher",
7+
products: [
8+
.library(
9+
name: "RailFenceCipher",
10+
targets: ["RailFenceCipher"])
11+
],
12+
dependencies: [],
13+
targets: [
14+
.target(
15+
name: "RailFenceCipher",
16+
dependencies: []),
17+
.testTarget(
18+
name: "RailFenceCipherTests",
19+
dependencies: ["RailFenceCipher"]),
20+
]
21+
)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
// Write your solution to the 'RailFenceCipher' exercise in this file.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Foundation
2+
import Testing
3+
4+
@testable import RailFenceCipher
5+
6+
let RUNALL = Bool(ProcessInfo.processInfo.environment["RUNALL", default: "false"]) ?? false
7+
8+
@Suite struct RailFenceCipherTests {
9+
10+
@Test("encode with two rails")
11+
func testEncodeWithTwoRails() {
12+
#expect(encode("XOXOXOXOXOXOXOXOXO", rails: 2) == "XXXXXXXXXOOOOOOOOO")
13+
}
14+
15+
@Test("encode with three rails", .enabled(if: RUNALL))
16+
func testEncodeWithThreeRails() {
17+
#expect(encode("WEAREDISCOVEREDFLEEATONCE", rails: 3) == "WECRLTEERDSOEEFEAOCAIVDEN")
18+
}
19+
20+
@Test("encode with ending in the middle", .enabled(if: RUNALL))
21+
func testEncodeWithEndingInTheMiddle() {
22+
#expect(encode("EXERCISES", rails: 4) == "ESXIEECSR")
23+
}
24+
25+
@Test("decode with three rails", .enabled(if: RUNALL))
26+
func testDecodeWithThreeRails() {
27+
#expect(decode("TEITELHDVLSNHDTISEIIEA", rails: 3) == "THEDEVILISINTHEDETAILS")
28+
}
29+
30+
@Test("decode with five rails", .enabled(if: RUNALL))
31+
func testDecodeWithFiveRails() {
32+
#expect(decode("EIEXMSMESAORIWSCE", rails: 5) == "EXERCISMISAWESOME")
33+
}
34+
35+
@Test("decode with six rails", .enabled(if: RUNALL))
36+
func testDecodeWithSixRails() {
37+
#expect(
38+
decode("133714114238148966225439541018335470986172518171757571896261", rails: 6)
39+
== "112358132134558914423337761098715972584418167651094617711286")
40+
}
41+
42+
}

0 commit comments

Comments
 (0)