never executed always true always false
    1 {-|
    2 Module:      Y2021.D14
    3 Description: Advent of Code 2021 Day 14 Solutions.
    4 License:     MIT
    5 Maintainer:  @tylerjl
    6 
    7 Solutions to the 2021 day 14 set of problems for <adventofcode.com>.
    8 -}
    9 module Y2021.D14
   10   ( parse14
   11   , part14A
   12   , part14B
   13   ) where
   14 
   15 import Data.Attoparsec.Text
   16 import Data.Either.Utils (fromRight)
   17 import Data.Foldable     (foldl')
   18 import Data.Map.Strict   (Map)
   19 import Data.Monoid
   20 import Data.MultiSet     (MultiSet)
   21 import Data.Text         (Text)
   22 import Data.Tuple.Extra  ((&&&))
   23 
   24 import qualified Data.Map.Strict as M
   25 import qualified Data.MultiSet   as S
   26 
   27 type PolySubs = Map String Char
   28 type Polymer = Map String (Sum Int)
   29 
   30 -- |Solution to part A
   31 part14A :: Text -> Int
   32 part14A = uncurry (solve14 10) . parse14
   33 
   34 -- |Solution to part B
   35 part14B :: Text -> Int
   36 part14B = uncurry (solve14 40) . parse14
   37 
   38 -- |Day 14 solution applicable to either parts A or B. Most of the dirty work
   39 -- happens in `react`, but this does the output calculation logic.
   40 solve14 :: Int -> Polymer -> PolySubs -> S.Occur
   41 solve14 iterations polymer subs =
   42   abs $
   43   uncurry subtract $
   44   (minimum &&& maximum) $
   45   map (adjust . snd) $
   46   S.toOccurList $ aggregate $ flip (!!) iterations $ iterate (react subs) polymer
   47   where
   48     adjust n
   49       | even n = n `div` 2
   50       | otherwise = (n + 1) `div` 2
   51 
   52 -- |Turn our map of tracked pairs into a `MultiSet` of characters. This number
   53 -- won't offer the solution (occurrences for each character) but just needs
   54 -- minor adjustments later to be accurate.
   55 aggregate :: Map [Char] (Sum S.Occur) -> MultiSet Char
   56 aggregate (M.toList -> chains) = foldl' summate S.empty chains
   57   where
   58     summate acc (chain, getSum -> n) = foldl' addTo acc $ zip (repeat n) chain
   59       where
   60         addTo multiset (instances, sym) =
   61           S.insertMany sym instances multiset
   62 
   63 -- |Function that's suitable to be fed into `iterate`. It's probably fairly
   64 -- memory-hungry since it builds up some big structures recursively, but it does
   65 -- the job.
   66 react :: PolySubs -> Polymer -> Polymer
   67 react subs polymer = foldl' expand M.empty (M.toList polymer)
   68   where
   69     expand newPoly (chain@[a, b], n) =
   70       case M.lookup chain subs of
   71         Nothing -> addSum newPoly chain n
   72         Just sub -> foldl' (uncurry . addSum) newPoly chains
   73           where chains = zip [[a, sub], [sub, b]] (repeat n)
   74     expand _ _ = error "unreachable"
   75 
   76 -- |Rearrange a few arguments to make inserting a running sum easier.
   77 addSum :: Polymer -> String -> Sum Int -> Polymer
   78 addSum set k v = M.insertWith mappend k v set
   79 
   80 -- |Pair up adjacent list elements into groups of two.
   81 twos :: [a] -> [[a]]
   82 twos (a:b:xs) = [a, b] : twos (b : xs)
   83 twos _ = []
   84 
   85 -- |Parse out a sequence of chars followed by the reaction translations.
   86 parse14 :: Text -> (Polymer, PolySubs)
   87 parse14 = fromRight . parseOnly (parser <* endOfLine <* endOfInput)
   88   where
   89     parser =
   90       (,) <$> (template <* endOfLine) <*>
   91       (M.fromList <$> sub `sepBy1` endOfLine)
   92     template =
   93       M.fromList . flip zip (repeat (Sum 1)) . twos <$> many1 cap <* endOfLine
   94     sub = (,) <$> caps <* string " -> " <*> cap
   95     caps = many1 cap
   96     cap = satisfy (`elem` ['A' .. 'Z'])