MonoAlphabetic and PolyAlphabetic ciphers in Haskell
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 -&gt; Char -&gt; Int -&gt; Char -&gt; Char
generalShift firstChar lastChar positions letter = ...


elem

elem is another potential expensive call. I would detect alphabetic characters using &lt;= comparisons with the end points of the upper- and lowercase character ranges:

isAlphabetic ch = ('a' &lt;= ch &amp;&amp; ch &lt;= 'z') || ('A' &lt;= ch &amp;&amp; ch &lt;= '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 -&gt; Int
toEnum :: Int -&gt; 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] -&gt; [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