Skip to content

Instantly share code, notes, and snippets.

@leeyspaul
Created July 31, 2018 23:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leeyspaul/e580b138405c5a6d1aad3f2ea8bad6b9 to your computer and use it in GitHub Desktop.
Save leeyspaul/e580b138405c5a6d1aad3f2ea8bad6b9 to your computer and use it in GitHub Desktop.
Caeser Decipher in Kotlin
// 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