Search code examples
haskellvigenere

Understanding Vigenere cipher in Haskell


I'm having trouble understanding what's exactly going on in the code below.

Specifically the recursive function call and "ks++[k]" as well as the chr $ 65. I'm assuming the former is used for recursively iterating through the list, but if someone could explain like i'm 5 I'd be very grateful.

vig (p:ps) (k:ks) = (encrypt p k):(vig ps (ks++[k])) 
  where
    encrypt b = chr $ 65 + mod (ord a + ord b) 26

full code

vig :: [Char] -> [Char] -> [Char]
vig [] k = []
vig p [] = []
vig (p:ps) (k:ks) = (encrypt p k):(vig ps (ks++[k]))
  where
    encrypt a b = chr $ 65 + mod (ord a + ord b) 26


Solution

  • The idea is that each character in the first list is "shifted" by a distance determined by the corresponding character in the second list. The second list, though, may have a different length than the first. If it is longer, there's no problem; you'll just ignore the extra (as seen by the base case vig [] k = []). If it is shorter, though, you'll want to start over at the beginning.

    Suppose you start with the call vig "horse" "dog". Then you will make the following recursive calls in order:

    vig "orse" "ogd"
    vig "rse" "gdo"
    vig "se" "dog"
    vig "e" "ogd"
    vig "" "gdo"
    

    encrypt is what does the actual encryption; it returns a new character based on the character p from the message and the character k from the encryption k. Each message character is used exactly once; each key character could be used multiple times (if the key is shorter than the message) or not at all (if the key is longer).

    A simpler (and more efficient way) to do this is to use the cycle function to create an infinite, repeating list from the key. (If ks is already long enough, it just makes an even longer list of characters you'll ultimately ignore. Because Haskell is lazy, there is no cost for this simplifying abstraction.)

    vig ps ks = vig' ps (cycle ks)
      where vig' _ [] = p
            vig' [] _ = []
            vig' (p:ps) (k:ks) = encrypt p k : vig' ps ks
            encrypt a b = ...
    

    However, vig' doesn't really care about the contents of either list or what encrypt does. It's kind of like map, except instead of building one list from another by apply a function to each element, it builds one list from two lists by applying a function to each corresponding pair of elements. This pattern is predefined as the zipWith function.

    vig ps ks = zipWith encrypt ps ks  -- vig = zipWith encrypt
      where encrypt a b = ...