-
Notifications
You must be signed in to change notification settings - Fork 0
/
16.hs
85 lines (68 loc) · 2.55 KB
/
16.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import Data.List (isPrefixOf, foldl')
import Runner (runner)
{-|
Solver for Day 16 of the Advent of Code 2020
Problem description: https://adventofcode.com/2020/day/16
-}
data Rule = Rule { label :: String, ranges :: [(Int, Int)] }
deriving (Eq, Show)
type Ticket = [Int]
main :: IO ()
main = runner solve1 solve2
solve1 :: String -> Int
solve1 input =
let (rules, _, tickets) = parseInput input
in sum $ concatMap (invalidFields rules) tickets
solve2 :: String -> Int
solve2 input =
let
(rules, ticket, tickets) = parseInput input
validTickets = filter (null . invalidFields rules) tickets
allRules = [rules | _ <- rules]
orderedRules = eliminate $ filterCandidateRules allRules validTickets
labelledTicket = zip (label <$> orderedRules) ticket
in product $ snd <$> filter (isPrefixOf "departure" . fst) labelledTicket
invalidFields :: [Rule] -> Ticket -> [Int]
invalidFields rules = filter (not . validByAnyRule rules)
isValid :: Rule -> Int -> Bool
isValid (Rule _ ranges) v = any (\(start, end) -> start <= v && v <= end) ranges
validByAnyRule :: [Rule] -> Int -> Bool
validByAnyRule rules v = any (`isValid` v) rules
filterCandidateRules :: [[Rule]] -> [Ticket] -> [[Rule]]
filterCandidateRules = foldl' $ zipWith (\rs f -> filter (`isValid` f) rs)
eliminate :: Eq a => [[a]] -> [a]
eliminate = map head . fixpoint (eliminate' . zipperAt 0)
where
remove e = map (filter (/= e))
eliminate' (t@(~(x:xs)), bs)
| null t = reverse bs
| [x'] <- x = eliminate' (remove x' xs, x : remove x' bs)
| otherwise = eliminate' (xs, x:bs)
fixpoint :: Eq a => (a -> a) -> a -> a
fixpoint f i =
let i' = f i
in if i' == i then i' else fixpoint f i'
zipperAt :: Int -> [a] -> ([a], [a])
zipperAt i xs = (drop i xs, reverse (take i xs))
parseInput :: String -> ([Rule], Ticket, [Ticket])
parseInput input =
let
[ruleLines, [_, ticketLine], _:ticketLines] = splitOn [""] $ lines input
rules = map parseRule ruleLines
ticket = map read $ splitOn "," ticketLine
tickets = map (map read . splitOn ",") ticketLines
in (rules, ticket, tickets)
parseRule :: String -> Rule
parseRule l =
let
[label, t] = splitOn ": " l
rangeSpans = splitOn " or " t
ranges = map ((\[s, e] -> (s, e)) . map read . splitOn "-") rangeSpans
in Rule label ranges
splitOn :: Eq a => [a] -> [a] -> [[a]]
splitOn s = splitOn' . zipperAt 0
where
splitOn' (t@(~(x:xs)), bs)
| null t = [reverse bs]
| s `isPrefixOf` t = reverse bs : splitOn' (drop (length s) t, [])
| otherwise = splitOn' (xs, x:bs)