|
| 1 | +import Text.Parsec |
| 2 | +import Control.Monad (void) |
| 3 | + |
| 4 | +main :: IO () |
| 5 | +main = interact $ (++ "\n") . show . ((,) <$> p1 <*> p2) . parseAlmanac |
| 6 | + |
| 7 | +data Almanac = Almanac { seeds :: [Int], maps :: [Map] } |
| 8 | +type Map = [RangeMapping] |
| 9 | +data RangeMapping = RangeMapping { from :: Range, to :: Range } |
| 10 | +data Range = Range { start :: Int, len :: Int } |
| 11 | + |
| 12 | +parseAlmanac :: String -> Almanac |
| 13 | +parseAlmanac s = case parse almanac "" s of |
| 14 | + Left err -> error (show err) |
| 15 | + Right v -> v |
| 16 | + where |
| 17 | + sp = char ' ' |
| 18 | + num = read <$> many1 digit |
| 19 | + nums = num `sepBy` sp |
| 20 | + seeds = string "seeds: " *> nums <* count 2 newline |
| 21 | + mapHeader = many1 (letter <|> char '-' <|> sp) >> char ':' |
| 22 | + endOfLineOrFile = void endOfLine <|> eof |
| 23 | + rangeMapping = mkRangeMapping <$> (num <* sp) <*> (num <* sp) <*> num |
| 24 | + map = mapHeader *> newline *> (rangeMapping `endBy` endOfLineOrFile) |
| 25 | + maps = map `endBy` endOfLineOrFile |
| 26 | + almanac = Almanac <$> seeds <*> maps |
| 27 | + mkRangeMapping a b c = RangeMapping (Range b c) (Range a c) |
| 28 | + |
| 29 | +p1 :: Almanac -> Int |
| 30 | +p1 Almanac { seeds, maps } = minimum $ map (`mTransform` maps) seeds |
| 31 | + |
| 32 | +-- Guide a seed through the transformations under the given maps |
| 33 | +mTransform :: Int -> [Map] -> Int |
| 34 | +mTransform = foldl rmTransform |
| 35 | + |
| 36 | +-- Transform a seed using the given range mappings |
| 37 | +rmTransform :: Int -> [RangeMapping] -> Int |
| 38 | +rmTransform s [] = s |
| 39 | +rmTransform s (rm:rms) = case rmApply rm s of |
| 40 | + Just s -> s |
| 41 | + Nothing -> rmTransform s rms |
| 42 | + |
| 43 | +-- Apply the given range mapping to the seed if it lies in the source range. |
| 44 | +rmApply :: RangeMapping -> Int -> Maybe Int |
| 45 | +rmApply RangeMapping { from, to } s = case offsetInRange from s of |
| 46 | + Nothing -> Nothing |
| 47 | + Just o -> Just (start to + o) |
| 48 | + |
| 49 | +-- If the given seed falls in the given range, then return its offset from the |
| 50 | +-- start of the range. |
| 51 | +offsetInRange :: Range -> Int -> Maybe Int |
| 52 | +offsetInRange Range { start, len } x = |
| 53 | + if x >= start && x <= (start + len) then Just (x - start) else Nothing |
| 54 | + |
| 55 | +p2 :: Almanac -> Int |
| 56 | +p2 Almanac { seeds, maps } = minimum . map start . filter (\r -> len r /= 0) $ |
| 57 | + foldl transformRanges (seedRanges seeds) maps |
| 58 | + |
| 59 | +seedRanges :: [Int] -> [Range] |
| 60 | +seedRanges [] = [] |
| 61 | +seedRanges (x:y:rest) = Range x y : seedRanges rest |
| 62 | + |
| 63 | +-- Transform seed ranges under the given range map. This may result in more |
| 64 | +-- ranges than we started with. |
| 65 | +transformRanges :: [Range] -> Map -> [Range] |
| 66 | +transformRanges rs m = concatMap (`transformRange` m) rs |
| 67 | + |
| 68 | +-- Transform a seed range under the given range mappings. Such a transformation |
| 69 | +-- may cause the range to split. |
| 70 | +transformRange :: Range -> [RangeMapping] -> [Range] |
| 71 | +transformRange r [] = [r] |
| 72 | +transformRange r (rm:rms) = concatMap transform (intersections r (from rm)) |
| 73 | + where transform x = case mapRange rm x of |
| 74 | + Nothing -> transformRange x rms |
| 75 | + Just y -> [y] |
| 76 | + |
| 77 | +-- Not necessarily symmetric. |
| 78 | +intersections :: Range -> Range -> [Range] |
| 79 | +intersections r@Range { start = s, len = n } r'@Range { start = s', len = n' } |
| 80 | + | s > e' = [r] |
| 81 | + | e < s' = [r] |
| 82 | + | s < s' = mk s (s' - 1) : if e <= e' then [mk s' e] else [mk s' e', mk (e' + 1) e] |
| 83 | + | s <= e' = if e <= e' then [mk s e] else [mk s e', mk (e' + 1) e] |
| 84 | + where e = s + n |
| 85 | + e' = s' + n' |
| 86 | + mk rs re = Range rs (re - rs) |
| 87 | + |
| 88 | +-- This is guaranteed to be called with a range that does not cross over the |
| 89 | +-- boundaries of the 'from' range mapping (i.e. either it falls completely |
| 90 | +-- within, or is completely outside). |
| 91 | +mapRange :: RangeMapping -> Range -> Maybe Range |
| 92 | +mapRange RangeMapping { from, to } r@Range { start = s, len = n } |
| 93 | + | s >= start from && s <= (start from + len from) = Just $ Range (s - start from + start to) n |
| 94 | + | otherwise = Nothing |
| 95 | + |
| 96 | +p2Brute :: Almanac -> Int |
| 97 | +p2Brute a = p1 $ a { seeds = expand (seeds a) } |
| 98 | + |
| 99 | +expand :: [Int] -> [Int] |
| 100 | +expand [] = [] |
| 101 | +expand (x:y:zs) = [x..(x+y)] ++ expand zs |
0 commit comments