Created
July 31, 2018 23:16
-
-
Save leeyspaul/e580b138405c5a6d1aad3f2ea8bad6b9 to your computer and use it in GitHub Desktop.
Caeser Decipher in Kotlin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Top level declaration of the alphabet array | |
val alphabet = arrayOf('a', | |
'b', | |
'c', | |
'd', | |
'e', | |
'f', | |
'g', | |
'h', | |
'i', | |
'j', | |
'k', | |
'l', | |
'm', | |
'n', | |
'o', | |
'p', | |
'q', | |
'r', | |
's', | |
't', | |
'u', | |
'v', | |
'w', | |
'x', | |
'y', | |
'z' | |
) | |
/** | |
* Takes a string and rotates all its letters by the specified offset. Uses a helper | |
* function to rotate each character in turn, and uses recursion to continue the process | |
* until all characters have been processed. | |
* | |
* @param s a string to encipher | |
* @param n an integer for the offset | |
* @return the enciphered String | |
* */ | |
fun encipher(s: String, n: Int): String { | |
require(n >= 0 && n <= 25) { "The value of integer n must be between 0 and 25." } | |
if (s.isEmpty()) { | |
return s | |
} else { | |
return encipherHelper(s[0], n) + encipher(s.substring(1), n) | |
} | |
} | |
/** | |
* Helper function called by encipher for each character in its input string. | |
* Checks if the input is a letter, stores if it is uppercase and normalises | |
* it to lower case. Looks up the character's index in an array of the alphabet | |
* and adds the offset to that index to determine the rotated character, switching | |
* it back to upper case if necessary. Returns non-letter characters unchanged. | |
* | |
* @param c the character to rotate | |
* @param n the offset integer | |
* @return the rotated character | |
* */ | |
fun encipherHelper(c: Char, n: Int): Char { | |
if (!c.isLetter()) { | |
return c | |
} | |
val isUpperCase = c == (c.toUpperCase()) | |
val normalised = c.toLowerCase() | |
val inputCharIdx = alphabet.indexOf(normalised) | |
return if (isUpperCase) alphabet[(inputCharIdx + n) % 26].toUpperCase() | |
else alphabet[(inputCharIdx + n) % 26] | |
} | |
/** | |
* Takes a String and attempts to decipher it using the cumulative relative frequency of its letters for each possible | |
* enciphering of it, using the percentage figures taken from Wikipedia (https://en.wikipedia.org/wiki/Letter_frequency) | |
* which cites Robert Lewand's "Cryptological Mathematics". Returns the enciphering of the input string which has the | |
* highest score. | |
* | |
* @param s String to decipher | |
* @return a deciphered String | |
* */ | |
fun decipher(s: String): String { | |
fun relativeFrequencies(letter: Char): Double = when (letter) { | |
'a' -> 8.167 | |
'b' -> 1.492 | |
'c' -> 2.782 | |
'd' -> 4.253 | |
'e' -> 12.702 | |
'f' -> 2.228 | |
'g' -> 2.015 | |
'h' -> 6.094 | |
'i' -> 6.966 | |
'j' -> 0.153 | |
'k' -> 0.772 | |
'l' -> 4.025 | |
'm' -> 2.406 | |
'n' -> 6.749 | |
'o' -> 7.507 | |
'p' -> 1.929 | |
'q' -> 0.095 | |
'r' -> 5.987 | |
's' -> 6.327 | |
't' -> 9.056 | |
'u' -> 2.758 | |
'v' -> 0.978 | |
'w' -> 2.360 | |
'x' -> 0.150 | |
'y' -> 1.974 | |
'z' -> 0.074 | |
else -> 0.0 | |
} | |
var bestScore = 0.0 | |
var bestMatch = "" | |
for (x in 1 until 26) { | |
val candidate = encipher(s, x) | |
val candidateNormalised = candidate.toLowerCase() | |
var score = 0.0 | |
for (letter in candidateNormalised) { | |
score += relativeFrequencies(letter) | |
} | |
if (score > bestScore) { | |
bestScore = score | |
bestMatch = candidate | |
} | |
} | |
return bestMatch | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment