|
|
|
@ -2,7 +2,9 @@ module Main where |
|
|
|
|
|
|
|
|
|
|
|
import Data.Char (digitToInt) |
|
|
|
import Data.Char (digitToInt) |
|
|
|
import Data.Either (rights) |
|
|
|
import Data.Either (rights) |
|
|
|
import Debug.Trace |
|
|
|
import Data.List (foldl') |
|
|
|
|
|
|
|
import Data.Map (Map) |
|
|
|
|
|
|
|
import qualified Data.Map as M |
|
|
|
import System.Environment (getArgs) |
|
|
|
import System.Environment (getArgs) |
|
|
|
|
|
|
|
|
|
|
|
-- | A candidate is either a left-most number we've just selected, or |
|
|
|
-- | A candidate is either a left-most number we've just selected, or |
|
|
|
@ -37,8 +39,43 @@ part1 = sum . map (maximum . map combine . rights . foldl go [] . map digitToInt |
|
|
|
combine :: (Int, Int) -> Int |
|
|
|
combine :: (Int, Int) -> Int |
|
|
|
combine (a, b) = a * 10 + b |
|
|
|
combine (a, b) = a * 10 + b |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type CandidateN = [Int] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- | Maintain one partial candidates for each length. |
|
|
|
|
|
|
|
-- |
|
|
|
|
|
|
|
-- Insight: If there are at least N-1 digits left after we've spotted |
|
|
|
|
|
|
|
-- the greatest digit, that digit needs to be the first digit of our |
|
|
|
|
|
|
|
-- candidates. |
|
|
|
|
|
|
|
-- |
|
|
|
|
|
|
|
-- Insight 2: We only ever need to store one candidate per length. |
|
|
|
part2 :: String -> Int |
|
|
|
part2 :: String -> Int |
|
|
|
part2 = error "Not implemented" |
|
|
|
part2 = sum . map (combine . (M.! 12) . foldl' checkPotential mempty . map digitToInt) . lines |
|
|
|
|
|
|
|
where |
|
|
|
|
|
|
|
checkPotential :: Map Int CandidateN -> Int -> Map Int CandidateN |
|
|
|
|
|
|
|
checkPotential cs x = |
|
|
|
|
|
|
|
let partials = filter ((< 12) . length) $ M.elems cs |
|
|
|
|
|
|
|
partials' = [x] : map (genPartial x) partials |
|
|
|
|
|
|
|
in foldl' insertMaxCandidate cs $ filter ((<= 12) . length) partials' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- generates new partials by appending x at candidate or after |
|
|
|
|
|
|
|
-- replacing a shedding smaller digits. |
|
|
|
|
|
|
|
genPartial :: Int -> CandidateN -> CandidateN |
|
|
|
|
|
|
|
genPartial x candidate = candidate ++ [x] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- replaces a candidate with a better one with the same length |
|
|
|
|
|
|
|
insertMaxCandidate :: Map Int CandidateN -> CandidateN -> Map Int CandidateN |
|
|
|
|
|
|
|
insertMaxCandidate m c = M.insertWith maxCandidate (length c) c m |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
maxCandidate :: CandidateN -> CandidateN -> CandidateN |
|
|
|
|
|
|
|
maxCandidate a b |
|
|
|
|
|
|
|
| combine a > combine b = a |
|
|
|
|
|
|
|
| otherwise = b |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
combine :: CandidateN -> Int |
|
|
|
|
|
|
|
combine xs = sum $ zipWith power [0 :: Int ..] (reverse xs) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
power :: Int -> Int -> Int |
|
|
|
|
|
|
|
power pow b = b * 10 ^ pow |
|
|
|
|
|
|
|
|
|
|
|
testInput :: String |
|
|
|
testInput :: String |
|
|
|
testInput = |
|
|
|
testInput = |
|
|
|
|