diff --git a/config.json b/config.json index 790af865d..b76317a94 100644 --- a/config.json +++ b/config.json @@ -483,6 +483,18 @@ "unlocked_by": "scrabble-score", "uuid": "4b3f7771-c642-4278-a3d9-2fb958f26361" }, + { + "core": false, + "difficulty": 6, + "slug": "rail-fence-cipher", + "topics": [ + "strings", + "loops", + "conditionals" + ], + "unlocked_by": "rotational-cipher", + "uuid": "6e4ad4ed-cc02-4132-973d-b9163ba0ea3d" + }, { "core": false, "difficulty": 7, diff --git a/exercises/rail-fence-cipher/.meta/src/reference/java/RailFenceCipher.java b/exercises/rail-fence-cipher/.meta/src/reference/java/RailFenceCipher.java new file mode 100644 index 000000000..0a165a36c --- /dev/null +++ b/exercises/rail-fence-cipher/.meta/src/reference/java/RailFenceCipher.java @@ -0,0 +1,64 @@ +import java.util.Arrays; + +class RailFenceCipher { + + private int key; + + RailFenceCipher(int key) { + this.key = key; + } + + String getEncryptedData(String message) { + String[] lines = splitIntoLines(message, false); + StringBuilder result = new StringBuilder(); + for (String line : lines) { + result.append(line); + } + return result.toString(); + } + + String getDecryptedData(String message) { + String[] lines = splitIntoLines(message, true); + + int charCount = 0; + for (int i = 0; i < key; ++i) { + while (lines[i].contains("?")) { + String letter = String.valueOf(message.charAt(charCount)); + lines[i] = lines[i].replaceFirst("\\?", letter); + charCount++; + } + } + + StringBuilder result = new StringBuilder(); + int lineCount = 0; + int direction = -1; + for (int i = 0; i < message.length(); ++i) { + String letter = String.valueOf(lines[lineCount].charAt(0)); + lines[lineCount] = lines[lineCount].substring(1); + result.append(letter); + direction *= lineCount == 0 || lineCount == key - 1 ? -1 : 1; + lineCount += direction; + } + return result.toString(); + } + + private String[] splitIntoLines(String message, boolean encrypted) { + String[] result = generateEmptyStrings(key); + int lineCount = 0; + int direction = -1; + for (char c : message.toCharArray()) { + String letter = String.valueOf(c); + result[lineCount] += encrypted ? "?" : letter; + direction *= lineCount == 0 || lineCount == key - 1 ? -1 : 1; + lineCount += direction; + } + return result; + } + + private String[] generateEmptyStrings(int num) { + String[] strings = new String[num]; + Arrays.fill(strings, ""); + return strings; + } + +} diff --git a/exercises/rail-fence-cipher/README.md b/exercises/rail-fence-cipher/README.md new file mode 100644 index 000000000..3b9a2035f --- /dev/null +++ b/exercises/rail-fence-cipher/README.md @@ -0,0 +1,78 @@ +# Rail Fence Cipher + +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. + + + +To run the tests: + +```sh +$ gradle test +``` + +For more detailed info about the Java track see the [help page](http://exercism.io/languages/java). + + +## Source + +[Wikipedia](https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher) + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. +i diff --git a/exercises/rail-fence-cipher/build.gradle b/exercises/rail-fence-cipher/build.gradle new file mode 100644 index 000000000..0bf827a6f --- /dev/null +++ b/exercises/rail-fence-cipher/build.gradle @@ -0,0 +1,17 @@ +apply plugin: "java" +apply plugin: "eclipse" +apply plugin: "idea" + +repositories { + mavenCentral() +} + +dependencies { + testCompile "junit:junit:4.12" +} +test { + testLogging { + exceptionFormat = 'full' + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/rail-fence-cipher/src/main/java/.keep b/exercises/rail-fence-cipher/src/main/java/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/rail-fence-cipher/src/test/java/RailFenceCipherTest.java b/exercises/rail-fence-cipher/src/test/java/RailFenceCipherTest.java new file mode 100644 index 000000000..df0dc3830 --- /dev/null +++ b/exercises/rail-fence-cipher/src/test/java/RailFenceCipherTest.java @@ -0,0 +1,55 @@ +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +public class RailFenceCipherTest { + + private RailFenceCipher railFenceCipher; + + @Test + public void encodeWithTwoRails() { + railFenceCipher = new RailFenceCipher(2); + Assert.assertEquals("XXXXXXXXXOOOOOOOOO", + railFenceCipher.getEncryptedData("XOXOXOXOXOXOXOXOXO")); + } + + @Ignore("Remove to run test") + @Test + public void encodeWithThreeRails() { + railFenceCipher = new RailFenceCipher(3); + Assert.assertEquals("WECRLTEERDSOEEFEAOCAIVDEN", + railFenceCipher.getEncryptedData("WEAREDISCOVEREDFLEEATONCE")); + } + + @Ignore("Remove to run test") + @Test + public void encodeWithEndingInTheMiddle() { + railFenceCipher = new RailFenceCipher(4); + Assert.assertEquals("ESXIEECSR", + railFenceCipher.getEncryptedData("EXERCISES")); + } + + @Ignore("Remove to run test") + @Test + public void decodeWithThreeRails() { + railFenceCipher = new RailFenceCipher(3); + Assert.assertEquals("THEDEVILISINTHEDETAILS", + railFenceCipher.getDecryptedData("TEITELHDVLSNHDTISEIIEA")); + } + + @Ignore("Remove to run test") + @Test + public void decodeWithFiveRails() { + railFenceCipher = new RailFenceCipher(5); + Assert.assertEquals("EXERCISMISAWESOME", + railFenceCipher.getDecryptedData("EIEXMSMESAORIWSCE")); + } + + @Ignore("Remove to run test") + @Test + public void decodeWithSixRails() { + railFenceCipher = new RailFenceCipher(6); + Assert.assertEquals("112358132134558914423337761098715972584418167651094617711286", + railFenceCipher.getDecryptedData("133714114238148966225439541018335470986172518171757571896261")); + } +} \ No newline at end of file diff --git a/exercises/settings.gradle b/exercises/settings.gradle index da2430a49..fe40a72a9 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -55,6 +55,7 @@ include 'poker' include 'prime-factors' include 'pythagorean-triplet' include 'queen-attack' +include 'rail-fence-cipher' include 'raindrops' include 'rectangles' include 'rna-transcription'