|
| 1 | +module Main where |
| 2 | + |
| 3 | +import System.IO |
| 4 | +import Data.List (elemIndex, find) |
| 5 | +import Data.Maybe (catMaybes, fromMaybe) |
| 6 | +import qualified Data.Set as Set |
| 7 | + |
| 8 | +parseFile :: FilePath -> IO [[Char]] |
| 9 | +parseFile path = do |
| 10 | + content <- readFile path |
| 11 | + return $ lines content |
| 12 | + |
| 13 | +findPositions :: Char -> [[Char]] -> [(Int, Int)] |
| 14 | +findPositions char arr = catMaybes [findPositionInLine char line row | (line, row) <- zip arr [0..]] |
| 15 | + |
| 16 | +findPositionInLine :: Char -> [Char] -> Int -> Maybe (Int, Int) |
| 17 | +findPositionInLine char line row = case elemIndex char line of |
| 18 | + Just col -> Just (row, col) |
| 19 | + Nothing -> Nothing |
| 20 | + |
| 21 | +getCharAtPosition :: [[Char]] -> (Int, Int) -> Char |
| 22 | +getCharAtPosition arr (row, col) = (arr !! row) !! col |
| 23 | + |
| 24 | +nextPositions :: Char -> (Int, Int) -> [(Int, Int)] |
| 25 | +nextPositions char (row, col) = case char of |
| 26 | + '|' -> [(row + 1, col), (row - 1, col)] |
| 27 | + '-' -> [(row, col + 1), (row, col - 1)] |
| 28 | + 'L' -> [(row - 1, col), (row, col + 1)] |
| 29 | + 'J' -> [(row - 1, col), (row, col - 1)] |
| 30 | + '7' -> [(row, col - 1), (row + 1, col)] |
| 31 | + 'F' -> [(row + 1, col), (row, col + 1)] |
| 32 | + '.' -> [] |
| 33 | + _ -> [] |
| 34 | + |
| 35 | +nextPosition :: [[Char]] -> (Int, Int) -> Char -> (Int, Int) -> Maybe (Int, Int) |
| 36 | +nextPosition arr prevPos curChar curPos = |
| 37 | + find (/= prevPos) (nextPositions curChar curPos) |
| 38 | + |
| 39 | +areConnected :: [[Char]] -> (Int, Int) -> (Int, Int) -> Bool |
| 40 | +areConnected arr (row1, col1) (row2, col2) = |
| 41 | + (row1, col1) `elem` nextPositions (getCharAtPosition arr (row2, col2)) (row2, col2) |
| 42 | + |
| 43 | +traverseLoop :: [[Char]] -> [(Int, Int)] -> (Int, Int) -> ((Int, Int), [(Int, Int)]) |
| 44 | +traverseLoop arr path curPos = |
| 45 | + let curChar = getCharAtPosition arr curPos |
| 46 | + in case curChar of |
| 47 | + 'S' -> (curPos, path) |
| 48 | + _ -> case nextPosition arr (last path) curChar curPos of |
| 49 | + Just nextPos -> traverseLoop arr (path ++ [curPos]) nextPos |
| 50 | + Nothing -> (curPos, path) |
| 51 | + |
| 52 | +isInBounds :: (Int, Int) -> [[a]] -> Bool |
| 53 | +isInBounds (row, col) arr = row >= 0 && row < length arr && col >= 0 && col < length (arr !! row) |
| 54 | + |
| 55 | +findPath :: [[Char]] -> [(Int, Int)] |
| 56 | +findPath arr = |
| 57 | + let (row, col) = head (findPositions 'S' arr) in |
| 58 | + let possibleNextPositions = [(row, col-1), (row, col+1), (row-1, col), (row-1, col-1), (row-1, col+1), (row+1, col), (row+1, col+1), (row+1, col-1)] in |
| 59 | + let inBoundsPos = filter (`isInBounds` arr) possibleNextPositions in |
| 60 | + let connectedPositions = filter (areConnected arr (row, col)) inBoundsPos in |
| 61 | + let firstPipePos = head connectedPositions in |
| 62 | + let (targetPos, path) = traverseLoop arr [(row, col)] firstPipePos in |
| 63 | + path |
| 64 | + |
| 65 | +closesLoop :: Char -> Char -> Bool |
| 66 | +closesLoop startChar endChar = case startChar of |
| 67 | + '7' -> endChar == 'F' |
| 68 | + 'J' -> endChar == 'L' |
| 69 | + _ -> False |
| 70 | + |
| 71 | +countPathHits :: Set.Set(Int,Int) -> [[Char]] -> Int -> Maybe Char -> (Int, Int) -> Int |
| 72 | +countPathHits pathPositions arr hits lastHit (row, col) = case col-1 of |
| 73 | + -2 -> hits |
| 74 | + x -> |
| 75 | + if (row, col) `elem` pathPositions |
| 76 | + then let curChar = arr !! row !! col in |
| 77 | + if (row, col+1) `elem` pathPositions && areConnected arr (row, col) (row, col+1 ) |
| 78 | + then |
| 79 | + if closesLoop (fromMaybe '.' lastHit) curChar |
| 80 | + then countPathHits pathPositions arr (hits - 1) Nothing (row, col-1) |
| 81 | + else countPathHits pathPositions arr hits lastHit (row, col-1) |
| 82 | + else countPathHits pathPositions arr (hits + 1) (Just curChar) (row, col-1) |
| 83 | + else countPathHits pathPositions arr hits Nothing (row, col-1) |
| 84 | + |
| 85 | +solve :: [[Char]] -> IO () |
| 86 | +solve arr = do |
| 87 | + let path = findPath arr |
| 88 | + print $ length path `div` 2 |
| 89 | + |
| 90 | +solve2 :: [[Char]] -> IO () |
| 91 | +solve2 arr = do |
| 92 | + let path = findPath arr |
| 93 | + let m = length arr |
| 94 | + let n = length (head arr) |
| 95 | + let pathPositions = Set.fromList path |
| 96 | + let allPossiblePositions = [(i, j) | i <- [0..m-1], j <- [0..n-1], not (Set.member (i, j) pathPositions)] |
| 97 | + let oddPositions = filter (\x -> odd (countPathHits pathPositions arr 0 Nothing x) ) allPossiblePositions |
| 98 | + print $ length $ oddPositions |
| 99 | + |
| 100 | +main :: IO () |
| 101 | +main = do |
| 102 | + arr <- parseFile "input" |
| 103 | + solve arr |
| 104 | + solve2 arr |
0 commit comments