This is my implementation using the dreadful !!
:
import Data.Char (chr, ord, toUpper)
-- A bit of self documentation help
type Key = String
type Msg = String
key :: Key
key = "TSTING"
msg :: Msg
msg = "I'm not even mad... This is impressive!"
-- | Checks if character is valid for encoding
isValid :: Char -> Bool
isValid c = let cUp = toUpper c :: Char
in 'A' <= cUp && cUp <= 'Z'
-- | Given 'key' & 'msg' generate a list of [Maybe Int] indices
-- to map 'msg' from 'key', skipping invalid characters
toIdx :: Key -> Msg -> [Maybe Int]
toIdx k m = map (flip mod keyN <$>) $ toIdx_ 0 m
where keyN = length k :: Int
toIdx_ :: Int -> Msg -> [Maybe Int]
toIdx_ _ "" = []
toIdx_ acc (c:cs)
| isValid c = Just acc : toIdx_ (acc + 1) cs
| otherwise = Nothing : toIdx_ acc cs
-- | Given 'key' & 'msg' generate a list of numbers representing
-- the amount to shift 'msg' characters based on 'key'
toShifts :: Key -> Msg -> [Int]
toShifts k m = map toKey (toIdx k m)
where kUp = map toUpper k :: Key
toKey :: Maybe Int -> Int
toKey Nothing = 0
toKey (Just x) = ord (kUp!!x) - ord 'A'
-- | Given 'by' & 'c', shift the Char 'c' by amount 'by'. 'by' can be both
-- positive & negative as well as 0.
shift :: Int -> Char -> Char
shift by c
| isValid c && c >= 'a' = shift_ $ ord 'a'
| isValid c && c >= 'A' = shift_ $ ord 'A'
| otherwise = c
where cONorm = ord (toUpper c) - ord 'A' :: Int
azN = ord 'Z' - ord 'A' :: Int
shift_ :: Int -> Char
shift_ aO = chr $ aO + mod (by + cONorm) azN
-- Encode & decode a message using the given key.
vigenere, unVigenere :: Key -> Msg -> Msg
vigenere k m = zipWith shift (toShifts k m) m
unVigenere k m = zipWith shift (map negate $ toShifts k m) m
I found that the most "annoying" thing when coming from background such as Python
is to be able to keep track of things, for example when figuring out how to convert valid characters into usable positions to be then mapped with the key
. That thing took me half a day to figure out!
How would you do it? Or is there some "standard" way of working with these sort of things? I'm referring particularly to toIdx
& toIdx_
, had to use toIdx_
to accumulate the indices with acc
in this list of Maybe Int
in order to correctly map valid Char
s with key
.
Of course, I wouldn't have it any other way but have an algorithm which takes any String
input and creates an encoded String
with preserved upper/lower-case and non-valid (out of ASCII alphabet) Char
s.