euler/Problem059.hs

54 lines
2.5 KiB
Haskell

-- Each character on a computer is assigned a unique code and the preferred
-- standard is ASCII (American Standard Code for Information Interchange). For
-- example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107.
--
-- A modern encryption method is to take a text file, convert the bytes to
-- ASCII, then XOR each byte with a given value, taken from a secret key. The
-- advantage with the XOR function is that using the same encryption key on the
-- cipher text, restores the plain text; for example, 65 XOR 42 = 107, then 107
-- XOR 42 = 65.
--
-- For unbreakable encryption, the key is the same length as the plain text
-- message, and the key is made up of random bytes. The user would keep the
-- encrypted message and the encryption key in different locations, and without
-- both "halves", it is impossible to decrypt the message.
--
-- Unfortunately, this method is impractical for most users, so the modified
-- method is to use a password as a key. If the password is shorter than the
-- message, which is likely, the key is repeated cyclically throughout the
-- message. The balance for this method is using a sufficiently long password
-- key for security, but short enough to be memorable.
--
-- Your task has been made easy, as the encryption key consists of three lower
-- case characters. Using cipher.txt (right click and 'Save Link/Target
-- As...'), a file containing the encrypted ASCII codes, and the knowledge that
-- the plain text must contain common English words, decrypt the message and
-- find the sum of the ASCII values in the original text.
import Euler
import Data.Attoparsec.Text
import Data.Bits
import Data.Char
import Debug.Trace
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.IO as T
numbersP = decimal `sepBy` char ',' <* optional endOfLine
data GSLWord = GSL { gslIndex :: Int, gslFrequency :: Int, gslWord :: T.Text }
gslWordP = GSL <$> decimal <* skipSpace <*> decimal <* skipSpace <*> takeWhile1 isPrint
gslListP = gslWordP `sepBy` endOfLine <* optional endOfLine
main = do
Right xs <- parseOnly numbersP <$> T.readFile "p059_cipher.txt"
Right gsl <- parseOnly (S.fromList . map gslWord <$> gslListP) <$> T.readFile "p059_gsl.txt"
let lcChars = map fromEnum ['a'..'z']
print $ snd $ maximumBy (compare `on` fst) $ do
key <- sequenceA $ replicate 3 lcChars
let ys = zipWith (xor) (cycle key) xs
guard $ all (\c -> c >= 1 && c <= 127) ys
let yt = T.pack $ map toEnum ys
return (length $ filter (`S.member` gsl) $ map (T.filter isAlpha) $ T.words yt, sum ys)