Difference between revisions of "Haskell Quiz/The Solitaire Cipher/Solution Thiago Arrais"

From HaskellWiki
Jump to navigation Jump to search
m
m
 
Line 5: Line 5:
   
 
import Char(chr, isAlpha, ord, toUpper)
 
import Char(chr, isAlpha, ord, toUpper)
import List(intersperse)
 
 
import System.Environment(getArgs, getProgName)
 
import System.Environment(getArgs, getProgName)
   
Line 31: Line 30:
   
 
-- Pushes a card (j) down once
 
-- Pushes a card (j) down once
push' j xs = if length right > 0
+
push' j xs = if not (null right)
 
then left ++ head right : j : tail right
 
then left ++ head right : j : tail right
 
else head left : j : tail left
 
else head left : j : tail left
Line 65: Line 64:
 
where newDeck = nextDeck oldDeck
 
where newDeck = nextDeck oldDeck
 
number deck@(n:_) = deck !! valueFor n
 
number deck@(n:_) = deck !! valueFor n
letter n = if isJoker n then "" else toLetter n : []
+
letter n = if isJoker n then "" else [toLetter n]
   
 
-- The keystream generated by an unkeyed deck
 
-- The keystream generated by an unkeyed deck
 
keystream = concat [c | (c,_) <- tail $ iterate stepStream ([], unkeyeddeck)]
 
keystream = concat [c | (c,_) <- tail $ iterate stepStream ([], unkeyeddeck)]
 
join = concat.intersperse " "
 
   
 
-- Combines an input string (xs) and the default keystream by applying the
 
-- Combines an input string (xs) and the default keystream by applying the
 
-- given operation (f). This is the function that does the encoding/decoding
 
-- given operation (f). This is the function that does the encoding/decoding
codeWith f xs = join.sanitize.letterize $
+
codeWith f xs = unwords.sanitize.letterize $
 
zipWith f (numberize letters) (numberize keyletters)
 
zipWith f (numberize letters) (numberize keyletters)
 
where keyletters = take (length letters) keystream
 
where keyletters = take (length letters) keystream
Line 87: Line 84:
 
-- An action that applies the coding function (f) to a set of words
 
-- An action that applies the coding function (f) to a set of words
 
-- and prints the resulting code
 
-- and prints the resulting code
printCode f = putStrLn . f . join
+
printCode f = putStrLn . f . unwords
   
 
main = do args <- getArgs
 
main = do args <- getArgs

Latest revision as of 11:18, 21 February 2010


module Main where

import Char(chr, isAlpha, ord, toUpper)
import System.Environment(getArgs, getProgName)

toUpperCase = map toUpper
toLetter n = chr $ ord 'A' + (n - 1) `mod` 26
toNumber l = ord l - ord 'A' + 1

split5 cs
    | length cs > 5 = (take 5 cs) : split5 (drop 5 cs)
    | otherwise     = [cs]

fill cs = cs ++ replicate (5 - length cs) 'X'

-- Filters alpha characters and splits them into groups of five
sanitize:: String -> [String]
sanitize cs = reverse $ (fill.head) rchunks : tail rchunks
    where rchunks = reverse.split5.filter isAlpha.toUpperCase $ cs

unkeyeddeck :: [Int]
unkeyeddeck = [1..54]

jokerA = 53
jokerB = 54
isJoker = (`elem` [jokerA, jokerB])

-- Pushes a card (j) down once
push' j xs = if not (null right)
             then left ++ head right : j : tail right
             else head left : j : tail left
    where (left,_:right) = break (== j) xs

-- Pushes a card (j) down a given number (n) of times
push j n = (!! n) . iterate (push' j)

pushJokerA = push jokerA 1
pushJokerB = push jokerB 2

-- Performs a triplecut around the first two cards that satisfy a predicate (p)
tripleCut p xs = bottom ++ j1 : (middle ++ j2 : top)
    where (top,j1:b1) = break p xs
          (middle,j2:bottom) = break p b1

countCut n xs = (reverse.tail $ rbottom) ++ top ++ [head rbottom]
    where top = take n xs
          rbottom = reverse.drop n $ xs

-- Performs a count cut by the number written on the bottom card
deckCut xs = countCut (last xs) xs

valueFor 54 = 53 -- B joker's value is 53
valueFor n = n

-- Shuffles the deck once
nextDeck = deckCut.tripleCut isJoker.pushJokerB.pushJokerA

-- Shuffles the deck once and extracts the resulting letter
stepStream :: (String, [Int]) -> (String, [Int])
stepStream (_, oldDeck) = (letter $ number newDeck, newDeck)
    where newDeck = nextDeck oldDeck
          number deck@(n:_) = deck !! valueFor n
          letter n = if isJoker n then "" else [toLetter n]

-- The keystream generated by an unkeyed deck
keystream = concat [c | (c,_) <- tail $ iterate stepStream ([], unkeyeddeck)]

-- Combines an input string (xs) and the default keystream by applying the
-- given operation (f). This is the function that does the encoding/decoding
codeWith f xs = unwords.sanitize.letterize $
                zipWith f (numberize letters) (numberize keyletters)
    where keyletters = take (length letters) keystream
          numberize = map toNumber
          letterize = map toLetter
          letters = concat $ sanitize xs

encode, decode :: String -> String
encode = codeWith (+)
decode = codeWith (-)

-- An action that applies the coding function (f) to a set of words
-- and prints the resulting code
printCode f = putStrLn . f . unwords

main = do args <- getArgs
          case args of
              ("d":ws@(_:_)) -> printCode decode ws
              ("e":ws@(_:_)) -> printCode encode ws
              _              -> getProgName >>=
                  \n -> putStrLn $ "Usage: " ++ n ++ " <d/e> <phrase>"