day14: Complete second puzzle

This commit is contained in:
Tristan Daniël Maat 2021-12-19 01:32:19 +00:00
parent 99ec5911b7
commit 7de9869d1c
Signed by: tlater
GPG key ID: 49670FD774E43268
2 changed files with 79 additions and 8 deletions

14
14/Itertools.hs Normal file
View file

@ -0,0 +1,14 @@
module Itertools (
flatmap,
windows
) where
windows :: Int -> [a] -> [[a]]
windows _ [] = []
windows i (x:xs)
| length xs < i-1 = []
| otherwise = (x:take (i-1) xs):windows i xs
flatmap :: (t -> [a]) -> [t] -> [a]
flatmap _ [] = []
flatmap f (x:xs) = f x ++ flatmap f xs

View file

@ -1,5 +1,8 @@
import Data.List (maximumBy, minimumBy)
import Data.Map.Strict (Map, (!)) import Data.Map.Strict (Map, (!))
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import Data.Ord (comparing)
import Itertools (flatmap, windows)
import Parsing (splitByString) import Parsing (splitByString)
import Reducers (leastMostOcc) import Reducers (leastMostOcc)
@ -9,6 +12,7 @@ main = do
let let
polymer = parsePolymer input polymer = parsePolymer input
(putStrLn . show . solution1) polymer (putStrLn . show . solution1) polymer
(putStrLn . show . solution2) polymer
parsePolymer :: String -> ([Char], Map [Char] Char) parsePolymer :: String -> ([Char], Map [Char] Char)
parsePolymer input = (template input, rules input) parsePolymer input = (template input, rules input)
@ -21,13 +25,18 @@ parsePolymer input = (template input, rules input)
tupelize untupled = (head untupled, (head . last) untupled) tupelize untupled = (head untupled, (head . last) untupled)
solution1 :: ([Char], Map [Char] Char) -> Int solution1 :: ([Char], Map [Char] Char) -> Int
solution1 (template, rules) = uncurry (-) (leastMostOcc polymer) solution1 (template, rules) = uncurry (-) (leastMostOcc (polymerizeN rules template 10))
where
polymer :: [Char]
polymer = iterate (polymerize rules) template !! 10
solution2 :: ([Char], Map [Char] Char) -> Int solution2 :: ([Char], Map [Char] Char) -> Int
solution2 (template, rules) = undefined solution2 (template, rules) = ((snd . most) polyCounts) - ((snd . least) polyCounts)
where
polyCounts :: Map Char Int
polyCounts = polyCounter rules template 40
least = minimumBy (comparing snd) . Map.assocs
most = maximumBy (comparing snd) . Map.assocs
polymerizeN :: Map [Char] Char -> [Char] -> Int -> [Char]
polymerizeN rules template i = iterate (polymerize rules) template !! i
polymerize :: Map [Char] Char -> [Char] -> [Char] polymerize :: Map [Char] Char -> [Char] -> [Char]
polymerize rules template = step template polymerize rules template = step template
@ -37,6 +46,54 @@ polymerize rules template = step template
step [final] = [final] step [final] = [final]
step (x:xs) = x:(rules ! [x, head xs]):(step xs) step (x:xs) = x:(rules ! [x, head xs]):(step xs)
-- Since we don't actually need to synthesize the polymer, but only
-- need to know how many of each elements occur, we can get around
-- having to create a ridiculously large list by operating on the rule
-- pairs instead.
--
-- E.g. NNCB -> NCNBCHB (see testInput1), in this case we turn:
--
-- - NN -> NC, CN
-- - NC -> NB, BC
-- - CB -> CH, HB
--
-- All we need to do is keep track of the counts of these
-- sub-sequences, not of the full polymer.
--
-- To get back to the individual elements, we then just need to
-- de-window it all again.
polyCounter :: Map [Char] Char -> [Char] -> Int -> Map Char Int
polyCounter rules template iterations = elCounts
where
initial :: Map [Char] Int
initial = (Map.fromListWith (+) . map (\e -> (e, 1)) . windows 2) template
step :: Map [Char] Int -> Map [Char] Int
step = Map.fromListWith (+) . flatmap resultingPolies . Map.assocs
resultingPolies :: ([Char], Int) -> [([Char], Int)]
resultingPolies (p, i) = [([head p, c], i), ([c, last p], i)]
where c = rules ! p
polyCounts :: Map [Char] Int
polyCounts = iterate step initial !! iterations
-- Because the windows are size 2, every element of a sub-binding is
-- counted twice (e.g. NBCBC -> NB, BC, CB, BC), except the first and
-- last element. We know the first and last elements (because we
-- operated on windows, they'll match the first and last of the
-- template), so we can un-window the whole batch by dividing their
-- number of occurrences by two, except for the first and last, where
-- we subtract the number by 1 first and add 1 back later.
elCounts :: Map Char Int
elCounts = Map.mapWithKey deWindow naiveSum
where
els :: ([Char], Int) -> [(Char, Int)]
els (p, i) = [(head p, i), (last p, i)]
naiveSum :: Map Char Int
naiveSum = (Map.fromListWith (+) . flatmap els . Map.assocs) polyCounts
deWindow :: Char -> Int -> Int
deWindow c i
| c == head template || c == last template = (i - 1) `quot` 2 + 1
| otherwise = i `quot` 2
-- Tests -- Tests
testInput1 = unlines [ testInput1 = unlines [
@ -59,6 +116,6 @@ testInput1 = unlines [
"CC -> N", "CC -> N",
"CN -> C"] "CN -> C"]
testInputParsed1 = parsePolymer testInput1 parsedTestInput1 = parsePolymer testInput1
test1 = solution1 testInputParsed1 -- 1588 test1 = solution1 parsedTestInput1 -- 1588
test2 = solution2 testInputParsed1 -- 2188189693529 test2 = solution2 parsedTestInput1 -- 2188189693529