{-# language DeriveFunctor #-} {-# language DeriveTraversable #-} {-# language RecordWildCards #-} import Control.Applicative (ZipList(ZipList), getZipList) import Control.Monad ((>=>), (<=<)) import Data.List (tails, unfoldr) import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty) import Data.Maybe (mapMaybe) change1 :: Char -> Int -> Char change1 'L' 0 = '#' change1 '#' n | 4 <= n = 'L' change1 c _ = c update1 :: [String] -> Maybe Char update1 ((ul:u:ur:_):(l:c:r:_):(bl:b:br:_):_) = pure . change1 c . length $ filter ('#' ==) [ul,u,ur,l,r,bl,b,br] update1 ([ul,u]:[l,c]:[bl,b]:_) = pure . change1 c . length $ filter ('#' ==) [ul,u,l,bl,b] update1 [(ul:u:ur:_),(l:c:r:_)] = pure . change1 c . length $ filter ('#' ==) [ul,u,ur,l,r] update1 [[ul,u],[l,c]] = pure . change1 c . length $ filter ('#' ==) [ul,u,l] update1 _ = Nothing update1All :: [String] -> [String] update1All = filter (not . null) . map (mapMaybe update1 . horiz) . vert vert :: [String] -> [[String]] vert [] = [] vert l@(h:_) = tails $ ('L' <$ h) : l horiz :: [String] -> [[String]] horiz [] = [] horiz l = getZipList $ traverse (ZipList . tails . ('L':)) l fixUpdate1 :: [String] -> [String] fixUpdate1 l = if u == l then u else fixUpdate1 u where u = update1All l data Zip a = MkZip { revBefore :: [a] , current :: a , future :: [a] } deriving (Eq, Show, Functor, Foldable, Traversable) instance Applicative Zip where pure x = MkZip { revBefore = repeat x, current = x, future = repeat x } f <*> x = MkZip { revBefore = zipWith ($) (revBefore f) (revBefore x) , current = current f (current x) , future = zipWith ($) (future f) (future x) } toZip :: NonEmpty a -> Zip a toZip (x:|xs) = MkZip { revBefore = [], current = x, future = xs } reverseOnto :: [a] -> [a] -> [a] reverseOnto [] l = l reverseOnto (h:t) l = reverseOnto t (h : l) toList :: Zip a -> [a] toList MkZip{..} = reverseOnto revBefore (current : future) moveNext :: Zip a -> Maybe (Zip a) moveNext MkZip{..} = fmap mn $ nonEmpty future where mn (x:|xs) = MkZip { revBefore = current : revBefore , current = x , future = xs } movePrev :: Zip a -> Maybe (Zip a) movePrev MkZip{..} = fmap mp $ nonEmpty revBefore where mp (x:|xs) = MkZip { revBefore = xs , current = x , future = current : future } moveD :: Zip (Zip a) -> Maybe (Zip (Zip a)) moveD = moveNext moveU :: Zip (Zip a) -> Maybe (Zip (Zip a)) moveU = movePrev moveR :: Zip (Zip a) -> Maybe (Zip (Zip a)) moveR = traverse moveNext moveL :: Zip (Zip a) -> Maybe (Zip (Zip a)) moveL = traverse movePrev search :: (Zip (Zip Char) -> Maybe (Zip (Zip Char))) -> Zip (Zip Char) -> Bool search step = search' where search' area = case step area of Nothing -> False Just next -> case current $ current next of '#' -> True 'L' -> False _ -> search' next change2 :: Char -> Int -> Char change2 'L' 0 = '#' change2 '#' n | 5 <= n = 'L' change2 c _ = c update2 :: Zip (Zip Char) -> Char update2 target = change2 (current $ current target) . length $ filter (flip search target) dirs where dirs = [ moveU >=> moveL, moveU, moveU >=> moveR , moveL, moveR , moveD >=> moveL, moveD, moveD >=> moveR ] update2All :: Zip (Zip Char) -> Zip (Zip Char) update2All = fmap (fmap update2 . traverse dupZ) . dupZ dupZ :: Zip a -> Zip (Zip a) dupZ z = MkZip { revBefore = unfoldr mp z , current = z , future = unfoldr mn z } where mp = fmap (\x -> (x,x)) . movePrev mn = fmap (\x -> (x,x)) . moveNext fixUpdate2 :: Zip (Zip Char) -> Zip (Zip Char) fixUpdate2 z = if z == u then z else fixUpdate2 u where u = update2All z interactive :: Show a => (String -> a) -> IO () interactive f = print . f =<< getContents part1 :: IO () part1 = interactive (sum . map (length . filter ('#' ==)) . fixUpdate1 . lines) main :: IO () main = interactive (fmap (sum . map (length . filter ('#' ==)) . toList . fmap toList . fixUpdate2 . toZip . fmap toZip) . (traverse nonEmpty <=< nonEmpty) . lines)