Note the following about
alphabet argument never changes throughout the recursive calls
- the only elements of
alphabet accessed are the first and last characters
last is a potentially expensive function since it traverses the entire list to find the last element
To elaborate on point #2, note that the following calls are exactly the same:
generalShift "abcdefg...xyz" 10 'x'
generalShift "az" 10 'x'
Given these observations, why not just pass the first and last characters of the alphabet to
generalShift :: Char -> Char -> Int -> Char -> Char
generalShift firstChar lastChar positions letter = ...
elem is another potential expensive call. I would detect alphabetic characters using
<= comparisons with the end points of the upper- and lowercase character ranges:
isAlphabetic ch = ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')
clean str = filter isAlphaBetic . map toLower
-- or the other way around: map toLower . filter isAlphabetic
There is a much faster way to compute the shift of a letter using the fact that Char is an enumeration (i.e. an instance of the Enum class) and therefore the following functions are available:
fromEnum :: Char -> Int
toEnum :: Int -> Char
These work like the
chr() functions in other languages.
Let's assume the first and last characters of the alphabet are the standard
z. I've leave you to figure out exactly what the code should look like, but here are some hints:
To shift a letter _n_ positions in the range a..z:
1. Determine how far away from 'a' the letter is.
2. Add n to that distance.
3. If the distance computed in step 2 would place it beyond 'z',
subtract the number of characters between 'a' and 'z' and
4. Add the distance computed in step 3 to the letter 'a'
and return it as the encoded letter.
There will be a bunch of calls to
toEnum as well as perhaps the
mod function might help here.
Nothing really wrong here, but I have a suggestion... it is customary to repeat the key if it isn't long enough for the entire message. I.e., if the key was
[3,2,1] and the message was
"Hello", you would repeat the key as many times as was needed to cover every letter of the message. The key used to encode the message in this case would be
You can use the
cycle function to repeat a list forever:
cycle :: [a] -> [a]
-- e.g. cycle [1,4,2] = [1,4,2,1,4,2,1,4,2,1,4,2...]
polyiAlphabeticCipher may be written as:
poliAlphabeticCipher keys text = zipWith shift (cycle keys) (clean text)
With that change
monoAlphabeticCipher may be implemented using
monoAlphabeticCipher shift text = poliAlphabeticCipher [shift] text