never executed always true always false
    1 {-|
    2 Module:      Y2021.D09
    3 Description: Advent of Code 2021 Day 09 Solutions.
    4 License:     MIT
    5 Maintainer:  @tylerjl
    6 
    7 Solutions to the 2021 day 09 set of problems for <adventofcode.com>.
    8 -}
    9 module Y2021.D09
   10   ( part9A
   11   , part9B
   12   , parse9
   13   )
   14   where
   15 
   16 import Data.Attoparsec.Text hiding (take)
   17 import Data.Either.Utils (fromRight)
   18 import Data.List (group, sort, sortOn)
   19 import Data.List.Extra (nubOrdOn)
   20 import Data.Maybe (catMaybes)
   21 import Data.Ord (Down(Down))
   22 import Data.Text (Text)
   23 import Math.Geometry.Grid
   24 import Math.Geometry.Grid.Square
   25 import Math.Geometry.GridMap hiding (map)
   26 import Math.Geometry.GridMap.Lazy (lazyGridMap, LGridMap)
   27 
   28 -- |Type alias for better readability.
   29 type Point = (Int, Int)
   30 -- |Type alias for better readability.
   31 type SeaFloor = LGridMap RectSquareGrid Int
   32 -- |Type alias for better readability.
   33 type Basins = LGridMap RectSquareGrid (Maybe (Point, Int))
   34 
   35 -- |Solve part A
   36 part9A :: Text -> Int
   37 part9A = sum . map (succ . snd) . nubOrdOn fst . findBasins . parse9
   38 
   39 -- |Solve part B
   40 part9B :: Text -> Int
   41 part9B =
   42   product .
   43   take 3 .
   44   sortOn Down . map length . group . sort . map fst . findBasins . parse9
   45 
   46 -- |Some common glue between our mapping function and extracting the values
   47 -- we're interested in.
   48 findBasins :: SeaFloor -> [(Point, Int)]
   49 findBasins = catMaybes . elems . basins
   50 
   51 -- |Recursive map over `SeaFloor` that turns each grid point into a possible
   52 -- representation of the basin this point flows to. `Grid` takes the brunt of
   53 -- the boilterplate here with `neighbours` and `mapWithKey`.
   54 basins :: SeaFloor -> Basins
   55 basins g = mapWithKey toBasins g
   56   where
   57     -- Although `basins` is the top-level map, toBasins is what we'll
   58     -- recursively call when we find a point that has to map to a low point
   59     -- somewhere.
   60     toBasins point value
   61       | value == 9      = Nothing
   62       | []  <- adjacent = Just (point, value)
   63       | otherwise       = minimum (map (uncurry toBasins) adjacent)
   64       where
   65         adjacent = [(x, g ! x) | x <- neighbours g point, g ! x < value]
   66 
   67 -- |Parse puzzle input into a `Grid`. I could probably do the conversion from
   68 -- `[[Int]]` to `Grid outside of the parser, but it's nice to go directly to the
   69 -- main data structure for the problem.
   70 parse9 :: Text -> SeaFloor
   71 parse9 = fromRight . parseOnly (grid <$> parser)
   72   where
   73     grid [] = error "empty input"
   74     grid rows@(row:_) =
   75       lazyGridMap (rectSquareGrid (length row) (length rows)) (concat rows)
   76     parser = line `sepBy1` endOfLine <* atEnd
   77     line = many1 (read . (: []) <$> digit)