Day 8: Two-Factor Authentication


In [1]:
inputLines = lines <$> readFile "input/day8.txt"

In [2]:
import Control.Lens (element, over)
import Control.Monad (forM_)
import Data.Char (isDigit)
import Data.List (isPrefixOf, transpose)

In [3]:
data Operation = Rect Int Int | RotateRow Int Int | RotateColumn Int Int deriving (Show)

In [4]:
parseOperation :: String -> Operation
parseOperation xs = command a b
    where
        command = snd $ head $ filter ((`isPrefixOf` xs) . fst) commandMapping
    
        commandMapping = [ ("rect",            Rect)
                         , ("rotate row y",    RotateRow)
                         , ("rotate column x", RotateColumn) ]

        a = parseNumber 0 xs
        b = parseNumber 1 xs
                
        -- Read the n-th number from a string
        parseNumber :: Int -> String -> Int
        parseNumber 0 = read . takeWhile isDigit . dropNonDigits
        parseNumber n = parseNumber (pred n) . dropWhile isDigit . dropNonDigits
        
        -- Drop the non-digits at the beginning of the string
        dropNonDigits :: String -> String
        dropNonDigits = dropWhile (not . isDigit)
        
-- Verify that it works
parseOperation "rotate row y=0 by 4"


RotateRow 0 4

In [5]:
-- Rotate the list by n positions.
-- The direction is such that the first items are moved to the back of the list.
rotate :: Int -> [a] -> [a]
rotate n xs = take len . drop ((-n) `mod` len) . cycle $ xs
    where len = length xs
    
-- Verify that it works
rotate 2 "1234567"


"6712345"

In [6]:
-- Rotate the a-th sublist by b positions
rotateSublist :: Int -> Int -> [[a]] -> [[a]]
rotateSublist a b = over (element a) (rotate b)

-- Verify that it works
rotateSublist 1 2 ["12345", "12345", "12345", "12345"]


["12345","45123","12345","12345"]

In [7]:
-- Apply an operation to a screen, which is represented by a list of rows
applyOperation :: [[Bool]] -> Operation -> [[Bool]]
applyOperation screen (Rect a b) = (map (turnOn a) $ (take b) screen) ++ drop b screen
    where
        turnOn :: Int -> [Bool] -> [Bool]
        turnOn n row = replicate n True ++ drop n row
applyOperation screen (RotateRow a b) = rotateSublist a b screen
applyOperation screen (RotateColumn a b) = transpose $ rotateSublist a b $ transpose screen

In [8]:
-- The screen has 6 rows and 50 columns. All pixels are off initially.
initialScreen = replicate 6 $ replicate 50 False

In [9]:
-- Get the final state of the screen by applying all operations.
finalScreen = foldl applyOperation initialScreen . map parseOperation <$> inputLines

Part 1: Count the pixels which are "on"


In [10]:
sum . map (length . filter id) <$> finalScreen


110

Part 2: Decipher the characters on the screen


In [11]:
-- Transform each row to a string, where "on" becomes "#".
screenToString :: [[Bool]] -> [String]
screenToString = map (map (\ b -> if b then '#' else ' '))

In [12]:
-- Print the result line by line and get "ZJHRKCPLYJ"
do
    s <- finalScreen
    forM_ (screenToString s) print


"####   ## #  # ###  #  #  ##  ###  #    #   #  ## "
"   #    # #  # #  # # #  #  # #  # #    #   #   # "
"  #     # #### #  # ##   #    #  # #     # #    # "
" #      # #  # ###  # #  #    ###  #      #     # "
"#    #  # #  # # #  # #  #  # #    #      #  #  # "
"####  ##  #  # #  # #  #  ##  #    ####   #   ##  "