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"
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"
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"]
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
In [10]:
sum . map (length . filter id) <$> finalScreen
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