generalShift
Note the following about generalShift
:
- the
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
, i.e.:
generalShift :: Char -> Char -> Int -> Char -> Char
generalShift firstChar lastChar positions letter = ...
elem
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
more generalShift
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 ord()
and chr()
functions in other languages.
Let's assume the first and last characters of the alphabet are the standard a
and 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
try again.
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 fromEnum
and toEnum
as well as perhaps the mod
function might help here.
poliAlphabeticCipher
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 [3,2,1,3,2]
.
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...]
and then polyiAlphabeticCipher
may be written as:
poliAlphabeticCipher keys text = zipWith shift (cycle keys) (clean text)
With that change monoAlphabeticCipher
may be implemented using poliAlphabeticCipher
:
monoAlphabeticCipher shift text = poliAlphabeticCipher [shift] text