Skip to content

Commit 02c3a12

Browse files
authored
feat: add practice exercise Rail Fence Cipher (#881)
1 parent 8ffd908 commit 02c3a12

File tree

12 files changed

+18245
-0
lines changed

12 files changed

+18245
-0
lines changed

config.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,22 @@
12061206
"practices": [],
12071207
"prerequisites": [],
12081208
"difficulty": 4
1209+
},
1210+
{
1211+
"slug": "rail-fence-cipher",
1212+
"name": "Rail Fence Cipher",
1213+
"uuid": "8a7f6f8c-ff36-4afd-9ab3-6dbd355d0263",
1214+
"practices": [],
1215+
"prerequisites": [
1216+
"basics",
1217+
"namespaces",
1218+
"conditionals",
1219+
"lists",
1220+
"loops",
1221+
"numbers",
1222+
"strings"
1223+
],
1224+
"difficulty": 4
12091225
}
12101226
],
12111227
"foregone": [
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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"authors": [
3+
"klimkin"
4+
],
5+
"files": {
6+
"solution": [
7+
"rail_fence_cipher.cpp",
8+
"rail_fence_cipher.h"
9+
],
10+
"test": [
11+
"rail_fence_cipher_test.cpp"
12+
],
13+
"example": [
14+
".meta/example.cpp",
15+
".meta/example.h"
16+
]
17+
},
18+
"blurb": "Implement encoding and decoding for the rail fence cipher.",
19+
"source": "Wikipedia",
20+
"source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher"
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include "rail_fence_cipher.h"
2+
3+
namespace rail_fence_cipher {
4+
5+
std::string encode(const std::string& plaintext, int num_rails) {
6+
if (num_rails <= 1) return plaintext;
7+
8+
const auto stride = num_rails * 2 - 2;
9+
const auto length = plaintext.size();
10+
11+
std::string result(length, ' ');
12+
auto dst = result.begin();
13+
14+
for (int rail = 0; rail < num_rails; ++rail) {
15+
for (size_t pos = rail; pos < length; pos += stride) {
16+
*dst++ = plaintext[pos];
17+
if (0 < rail && rail < num_rails - 1) {
18+
size_t next_pos = pos + stride - rail * 2;
19+
if (next_pos < length) *dst++ = plaintext[next_pos];
20+
}
21+
}
22+
}
23+
24+
return result;
25+
}
26+
27+
std::string decode(const std::string& ciphertext, int num_rails) {
28+
if (num_rails <= 1) return ciphertext;
29+
30+
const auto stride = num_rails * 2 - 2;
31+
const auto length = ciphertext.size();
32+
33+
std::string result(length, ' ');
34+
auto src = ciphertext.cbegin();
35+
36+
for (int rail = 0; rail < num_rails; ++rail) {
37+
for (size_t pos = rail; pos < length; pos += stride) {
38+
result[pos] = *src++;
39+
if (0 < rail && rail < num_rails - 1) {
40+
size_t next_pos = pos + stride - rail * 2;
41+
if (next_pos < length) result[next_pos] = *src++;
42+
}
43+
}
44+
}
45+
46+
return result;
47+
}
48+
49+
} // namespace rail_fence_cipher
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace rail_fence_cipher {
6+
7+
std::string encode(const std::string& plaintext, int num_rails);
8+
std::string decode(const std::string& ciphertext, int num_rails);
9+
10+
} // namespace rail_fence_cipher
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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Get the exercise name from the current directory
2+
get_filename_component(exercise ${CMAKE_CURRENT_SOURCE_DIR} NAME)
3+
4+
# Basic CMake project
5+
cmake_minimum_required(VERSION 3.5.1)
6+
7+
# Name the project after the exercise
8+
project(${exercise} CXX)
9+
10+
# Get a source filename from the exercise name by replacing -'s with _'s
11+
string(REPLACE "-" "_" file ${exercise})
12+
13+
# Implementation could be only a header
14+
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file}.cpp)
15+
set(exercise_cpp ${file}.cpp)
16+
else()
17+
set(exercise_cpp "")
18+
endif()
19+
20+
# Use the common Catch library?
21+
if(EXERCISM_COMMON_CATCH)
22+
# For Exercism track development only
23+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h $<TARGET_OBJECTS:catchlib>)
24+
elseif(EXERCISM_TEST_SUITE)
25+
# The Exercism test suite is being run, the Docker image already
26+
# includes a pre-built version of Catch.
27+
find_package(Catch2 REQUIRED)
28+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h)
29+
target_link_libraries(${exercise} PRIVATE Catch2::Catch2WithMain)
30+
# When Catch is installed system wide we need to include a different
31+
# header, we need this define to use the correct one.
32+
target_compile_definitions(${exercise} PRIVATE EXERCISM_TEST_SUITE)
33+
else()
34+
# Build executable from sources and headers
35+
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h test/tests-main.cpp)
36+
endif()
37+
38+
set_target_properties(${exercise} PROPERTIES
39+
CXX_STANDARD 17
40+
CXX_STANDARD_REQUIRED OFF
41+
CXX_EXTENSIONS OFF
42+
)
43+
44+
set(CMAKE_BUILD_TYPE Debug)
45+
46+
if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(GNU|Clang)")
47+
set_target_properties(${exercise} PROPERTIES
48+
COMPILE_FLAGS "-Wall -Wextra -Wpedantic -Werror"
49+
)
50+
endif()
51+
52+
# Configure to run all the tests?
53+
if(${EXERCISM_RUN_ALL_TESTS})
54+
target_compile_definitions(${exercise} PRIVATE EXERCISM_RUN_ALL_TESTS)
55+
endif()
56+
57+
# Tell MSVC not to warn us about unchecked iterators in debug builds
58+
if(${MSVC})
59+
set_target_properties(${exercise} PROPERTIES
60+
COMPILE_DEFINITIONS_DEBUG _SCL_SECURE_NO_WARNINGS)
61+
endif()
62+
63+
# Run the tests on every build
64+
add_custom_target(test_${exercise} ALL DEPENDS ${exercise} COMMAND ${exercise})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "rail_fence_cipher.h"
2+
3+
namespace rail_fence_cipher {
4+
5+
std::string encode(const std::string& plaintext, int num_rails) {}
6+
7+
std::string decode(const std::string& ciphertext, int num_rails) {}
8+
9+
} // namespace rail_fence_cipher
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace rail_fence_cipher {
6+
7+
std::string encode(const std::string& plaintext, int num_rails);
8+
std::string decode(const std::string& ciphertext, int num_rails);
9+
10+
} // namespace rail_fence_cipher
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "rail_fence_cipher.h"
2+
#ifdef EXERCISM_TEST_SUITE
3+
#include <catch2/catch.hpp>
4+
#else
5+
#include "test/catch.hpp"
6+
#endif
7+
8+
TEST_CASE("encode with two rails", "[46dc5c50-5538-401d-93a5-41102680d068]") {
9+
REQUIRE(rail_fence_cipher::encode("XOXOXOXOXOXOXOXOXO", 2) ==
10+
"XXXXXXXXXOOOOOOOOO");
11+
}
12+
13+
#if defined(EXERCISM_RUN_ALL_TESTS)
14+
15+
TEST_CASE("encode with three rails", "[25691697-fbd8-4278-8c38-b84068b7bc29]") {
16+
REQUIRE(rail_fence_cipher::encode("WEAREDISCOVEREDFLEEATONCE", 3) ==
17+
"WECRLTEERDSOEEFEAOCAIVDEN");
18+
}
19+
20+
TEST_CASE("encode with ending in the middle",
21+
"[384f0fea-1442-4f1a-a7c4-5cbc2044002c]") {
22+
REQUIRE(rail_fence_cipher::encode("EXERCISES", 4) == "ESXIEECSR");
23+
}
24+
25+
TEST_CASE("decode_with_three_rails", "[cd525b17-ec34-45ef-8f0e-4f27c24a7127]") {
26+
REQUIRE(rail_fence_cipher::decode("TEITELHDVLSNHDTISEIIEA", 3) ==
27+
"THEDEVILISINTHEDETAILS");
28+
}
29+
30+
TEST_CASE("decode_with_five_rails", "[dd7b4a98-1a52-4e5c-9499-cbb117833507]") {
31+
REQUIRE(rail_fence_cipher::decode("EIEXMSMESAORIWSCE", 5) ==
32+
"EXERCISMISAWESOME");
33+
}
34+
35+
TEST_CASE("decode_with_six_rails", "[93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3]") {
36+
REQUIRE(rail_fence_cipher::decode(
37+
"133714114238148966225439541018335470986172518171757571896261",
38+
6) ==
39+
"112358132134558914423337761098715972584418167651094617711286");
40+
}
41+
42+
#endif

0 commit comments

Comments
 (0)