{-# LANGUAGE TupleSections, ViewPatterns #-}
{-# OPTIONS_HADDOCK ignore-exports #-}
module Day21 (day21, day21a, day21b) where
import Data.List (foldl', transpose)
import Data.List.Split (chunksOf, splitOn)
import qualified Data.Map.Lazy as Map ((!), assocs, empty, fromList, fromListWith, insert, map, member, unionsWith)
import Data.Map.Lazy (Map)
transforms :: [[[a]] -> [[a]]]
transforms = [id, f, g, f . g, t, t . f, t . g, t . f . g] where
f = reverse
g = map reverse
t = transpose
parse :: String -> Map [[Bool]] [[Bool]]
parse = Map.fromList . concatMap parseLine . lines where
parseLine s = let [a, "=>", b] = words s in
[ (transform a', b')
| let a' = map (== '#') <$> splitOn "/" a
, let b' = map (== '#') <$> splitOn "/" b
, transform <- transforms
]
start :: [[Bool]]
start = map (== '#') <$> splitOn "/" ".#./..#/###"
step :: (Ord a) => Map [[a]] [[a]] -> [[a]] -> [[a]]
step rules grid = assemble $ map (rules Map.!) exploded where
(d, n):_ = [(d, n) | d <- [2..], (n, 0) <- [length grid `divMod` d]]
exploded = chunksOf d grid >>= transpose . map (chunksOf d)
assemble parts = chunksOf n parts >>= map concat . transpose
canonicalize :: (Ord a) => [[a]] -> [[a]]
canonicalize grid = minimum [t grid | t <- transforms]
step3 rules (canonicalize -> grid) n = iterate expand3 [(grid, 1)] !! n where
rules3 = step' Map.empty grid
step' expansions grid
| Map.member grid expansions = expansions
| otherwise = foldl' step' (Map.insert grid counts expansions) parts
where
parts = chunksOf 3 (iterate (step rules) grid !! 3) >>=
map canonicalize . transpose . map (chunksOf 3)
counts = Map.fromListWith (+) $ (, 1) <$> parts
expand3 counts = Map.assocs $ Map.unionsWith (+)
[Map.map (* count) $ rules3 Map.! grid | (grid, count) <- counts]
day21 :: Int -> String -> Int
day21 n (parse -> rules) = sum
[ count * length (iterate (step rules) grid !! r >>= filter id)
| (grid, count) <- step3 rules start q
] where (q, r) = n `quotRem` 3
day21a :: String -> Int
day21a = day21 5
day21b :: String -> Int
day21b = day21 18