language: haskell prompt: https://adventofcode.com/2020/day/7
learning:
#!/usr/bin/env runhaskell
import Control.Arrow
import Control.Monad
import Data.Char
import Data.List
import Data.List.Split
import qualified Data.Map.Strict as M
import Data.Tree
import System.IO
main :: IO ()
main = do
file <- readFile "./input"
let fileLines = map (splitOn " contain ") . filter (\l -> length l > 0) . lines $ file
putStrLn . solve1 . forestifySparse $ fileLines
putStrLn . solve2 . forestifyFull $ fileLines
solve1 :: Forest String -> String
solve1 = filter (\tree -> elem "shiny gold" tree)
>>> length
>>> pred -- Ignore the "self" case
>>> show
>>> (++ " bags can contain at least 1 \"shiny gold\"")
solve2 :: Forest String -> String
solve2 = filter (\(Node b _) -> b == "shiny gold")
>>> head
>>> length
>>> pred -- Ignore the "self" case
>>> show
>>> (++ " bags required in \"shiny gold\"")
forestifySparse = forestify False
forestifyFull = forestify True
-- Woof. This should be refactored and/or broken up a bit for real code.
forestify :: Bool -> [[String]] -> Forest String
forestify isFull rawLines = unfoldForest toNode (map fst cleanLines)
where
toNode x = (x, filter (/= "no other") $ precedenceMap M.! x)
precedenceMap :: M.Map String [String]
precedenceMap = M.fromList cleanLines
cleanLines :: [(String, [String])]
cleanLines = map cleanLine rawLines
cleanLine [outside, insides]
= (cleanBit outside, (if isFull then cleanBitsFull else cleanBitsSparse) . uncomma $ insides)
cleanBit = head . splitOn " bag"
cleanBitsSparse = map (\entry -> if isDigit (head entry) then unwords . tail . words $ entry else entry ) . map cleanBit
cleanBitsFull = concat . map (\entry -> if isDigit (head entry) then take (read . head . words $ entry) (repeat (unwords . tail . words $ entry)) else [entry] ) . map cleanBit
uncomma = splitOn ", "