aboutsummaryrefslogtreecommitdiffstats
path: root/day10/main.hs
blob: a368cddc75e3d18486a3aa14c3b0c245a7d82dde (plain)
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
module Main where

import Data.Either (fromLeft, fromRight)
import Data.List (foldl', sort)
import Data.Map (Map, (!))
import qualified Data.Map as Map
import Utils

data Expr
  = Paren [Expr]
  | Bracket [Expr]
  | Curly [Expr]
  | Angle [Expr]
  deriving (Show)

data Failure
  = Incomplete
  | Corrupted Char
  deriving (Show)

type Computation a = Either Failure a

consume expected (c : cs) =
  if c /= expected
    then Left (Corrupted c)
    else Right cs
consume expected [] = Left Incomplete

pairs =
  Map.fromList
    [ ('(', ')'),
      ('[', ']'),
      ('{', '}'),
      ('<', '>')
    ]

opening = Map.keys pairs

closing = Map.elems pairs

expr :: Char -> [Expr] -> Expr
expr '(' children = Paren children
expr '[' children = Bracket children
expr '{' children = Curly children
expr '<' children = Angle children
expr c _ = error $ "invalid c: " ++ [c]

parse :: String -> Computation ([Expr], String)
parse s
  | null s = Left Incomplete
  | head s `elem` opening =
    let (c : cs) = s
        closing = pairs ! c
     in do
          (children, rest) <- parse cs
          rest' <- consume closing rest
          (nextExprs, rest'') <-
            if not (null rest')
              then parse rest'
              else Right ([], rest')
          Right (expr c children : nextExprs, rest'')
  | head s `elem` closing = Right ([], s)
  | otherwise = error "unreachable"

isCorrupted expr = case expr of
  Left (Corrupted c) -> True
  _ -> False

fromCorrupted expr = case expr of
  Left (Corrupted c) -> c
  a -> error $ "not corrupted: " ++ show a

score1 ')' = 3
score1 ']' = 57
score1 '}' = 1197
score1 '>' = 25137
score1 a = error $ "not valid for scoring: " ++ [a]

e1 rows =
  let parsed = map parse rows
      corrupted = parsed $> filter isCorrupted .> map fromCorrupted
      scores = corrupted $> map score1
   in sum scores

repair :: [Char] -> [Char] -> Either Failure [Char]
repair expectStack s
  | null s = Right expectStack
  | head s `elem` opening =
    let (c : cs) = s
        correspondingClosing = pairs ! c
        expectStack' = correspondingClosing : expectStack
     in repair expectStack' cs
  | head s `elem` closing =
    do
      let (c : cs) = s
      let (ec : ecs) = expectStack
      if c /= ec
        then Left (Corrupted c)
        else repair ecs cs
  | otherwise = error "unreachable"

score2 ')' = 1
score2 ']' = 2
score2 '}' = 3
score2 '>' = 4
score2 a = error $ "not valid for scoring: " ++ [a]

fixToScore = foldl' (\acc c -> 5 * acc + score2 c) 0

e2 rows =
  let parsed = map (repair []) rows
      fixesM = parsed $> filter (not . isCorrupted)
      fixes = fixesM $> map (\(Right fix) -> fix)
      scores = fixes $> map fixToScore
      scoresSorted = sort scores
      scoresN = length scoresSorted
   in scoresSorted !! (scoresN `div` 2)

main :: IO ()
main =
  do
    contents <- getContents

    let input = contents $> lines

    e1 input $> print
    e2 input $> print