import Data.Char import Data.List import Data.List.Split import Linear.V2 main :: IO () main = do input <- init . splitOn "\n" <$> readFile "input" print $ day3a input print $ day3b input parse :: [String] -> [(V2 Int, Int)] parse = concat . zipWith (parseLine 0) [0 ..] parseLine :: Int -> Int -> String -> [(V2 Int, Int)] parseLine _ _ [] = [] parseLine x y (s:ss) | isDigit s = (V2 x y, read num) : parseLine (x + length num) y (dropWhile isDigit ss) | otherwise = parseLine (succ x) y ss where num = takeWhile isDigit (s : ss) getSurround :: (V2 Int, Int) -> [V2 Int] getSurround (V2 x y, n) = line (pred y) ++ line (succ y) ++ [V2 (pred x) y, V2 (x + l) y] where l = length $ show n line c = map (`V2` c) [pred x .. l + x] checkPart :: (Char -> Bool) -> [String] -> V2 Int -> Bool checkPart p input (V2 x y) | x < 0 || y < 0 = False | x >= length (head input) || y >= length input = False | otherwise = p (input !! y !! x) day3a :: [String] -> Int day3a input = sum $ map snd $ filter (any (checkPart (`notElem` ('.' : ['0' .. '9'])) input) . getSurround) (parse input) day3b :: [String] -> Int day3b input = sum $ map (product . map (snd . fst)) $ filter (\x -> length x >= 2) $ groupBy (\(_, x) (_, y) -> x == y) $ sortOn snd candidates where candidates = [ (x, y) | x <- parse input , y <- getSurround x , _ <- filter (checkPart (== '*') input) [y] ]