I've worked out a functioning Vigenère encryption in Scala, and while it doesn't seem like my code is too hideous, I'm sure I could make it more functional and Scala-esque. Primarily, it seems like:
- I've made the full key with a for-expression / zipWithIndex, I'm wondering if this is the best way to do this though. Would it be better to do this inside the encipher() function? Using an anonymous function? The way I have it now works, but I'm curious if there's a way to do so that'd be more in line with Scala best practices.
- The actual encryption portion, specifically the output for
case (c, i)
, is sort of messy/convoluted, and it seems like there might be a better way to do it.
Of course, I'll be grateful for any feedback. Definitely still getting my feet wet with Scala, and any help getting into a more functional state of mind would be appreciated.
object Vigenere {
// alphabet for use throughout object
private val alphabet = ('A' to 'Z').toList
//empty array to be filled with fillTabula() function
private var tabulaRecta = Array.ofDim[Char](26, 26)
/* fills the tabula recta array
** creates 26 rows of alphabets, each alphabet shifted
** left by one letter each iteration */
def fillTabula() = {
for (row <- 0 to 25) {
for (col <- 0 to 25) {
tabulaRecta(row)(col) = alphabet((row + col) % 26)
}
}
}
/* create the full key by repeating the key until its as long
** as the plaintext. */
def makeFullkey(key: String, text:String): String = {
val fkey = for (c <- text.zipWithIndex) yield c match {
case (c, i) => key(i % key.length)
}
return fkey.mkString
}
def encipher(key: String, text:String): String = {
// fill the tabula recta
fillTabula()
val fkey = makeFullkey(key, text)
/* to encipher, zip the plaintext to return each letter with
** its index. if it's a space, just add space to ciphertext.
** if it's not a space, find the corresponding row in the
** tabula recta, then go to the appropriate column to
** return the ciphertext character.
** the second case here is a mess, there's got to be a nicer way. */
val ciphertext = for (c <- text.zipWithIndex) yield c match {
case (' ', _) => ' '
case (c, i) => tabulaRecta(alphabet.indexOf(fkey(i)))(alphabet.indexOf(c))
}
return ciphertext.mkString
}
def main(args: Array[String]) {
val Array(text, key) = args
println("Plaintext: " + text)
println("Key: " + key)
println("Ciphertext: " + encipher(key.toUpperCase, text.toUpperCase))
}
}
Edit: I realized that I could make the fullkey using zipWithIndex as well, and also found a better place to use toUpperCase. I've changed the code and updated my question accordingly.
1 Answer 1
I'm not a scala/fp expert but here is my 2 cents.
Your code reads very much like you have read the definition on wikipedia and translated it into scala. As the wikipedia definition is written as a human implemented algorithm you end up with a very procedural approach.
A few specific things:
- tabulaRecta - this is constant, making it a var is a massive code smell
- makeFullkey - you use zipWithIndex but don't use c, just use index
- why can't tabulaRecta be a function?
- why create a fullKey - can't you compute the key char as a function?
-
\$\begingroup\$ Hmm, I honestly have no idea how I'd go about making tabulaRecta into a function at this point, I'd certainly prefer it being a val too. I'm sure you're right about the fullKey thing too. I used zipWithIndex on the fullKey so as to avoid using an accumulating counter variable, but I'm sure there's a better way to do that too. Thanks for giving me some stuff to think about! \$\endgroup\$djc– djc2015年07月29日 16:01:38 +00:00Commented Jul 29, 2015 at 16:01
-
\$\begingroup\$ Let me know if want more help, I can post some actual code as well. I thought it best just to give you pointers to start with. \$\endgroup\$brain– brain2015年07月29日 16:37:17 +00:00Commented Jul 29, 2015 at 16:37
-
\$\begingroup\$ Appreciate it! I'll see what I can come up with and come back if I can't figure anything out. \$\endgroup\$djc– djc2015年07月29日 16:42:04 +00:00Commented Jul 29, 2015 at 16:42