-
Notifications
You must be signed in to change notification settings - Fork 0
/
Solution.hs
117 lines (95 loc) · 3.22 KB
/
Solution.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
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
module Day04.Solution
( part1,
part2,
parsePassports,
isValidLoose,
isValidStrict,
strictPassportValidations,
Validations (..),
)
where
import Advent.Utils (isBetween, readInt)
import Data.Either (fromRight, isRight)
import qualified Data.Map.Strict as Map
import Data.Maybe (isJust)
import Text.Parsec
part1 :: String -> String
part1 = show . length . filter isValidLoose . fromRight [] . parsePassports
part2 :: String -> String
part2 = show . length . filter isValidStrict . fromRight [] . parsePassports
type Key = String
type Value = String
type Passport = Map.Map Key Value
parsePassports :: String -> Either ParseError [Passport]
parsePassports = parse (passportParser `sepEndBy1` newline) ""
passportParser :: Parsec String st Passport
passportParser = Map.fromList <$> fieldParser `sepEndBy1` space
fieldParser :: Parsec String st (Key, Value)
fieldParser = (,) <$> keyParser <*> (char ':' *> valueParser)
valueParser :: Parsec String st Value
valueParser = many1 (alphaNum <|> char '#')
keyParser :: Parsec String st Key
keyParser = many1 alphaNum
isValidLoose :: Passport -> Bool
isValidLoose = isValidWith loosePassportValidations
isValidStrict :: Passport -> Bool
isValidStrict = isValidWith strictPassportValidations
type ValidateField = Maybe Value -> Bool
data Validations = Validations
{ byr :: ValidateField,
iyr :: ValidateField,
eyr :: ValidateField,
hgt :: ValidateField,
hcl :: ValidateField,
ecl :: ValidateField,
pid :: ValidateField
}
isValidWith :: Validations -> Passport -> Bool
isValidWith validations =
and
. sequenceA
[ byr validations . Map.lookup "byr",
iyr validations . Map.lookup "iyr",
eyr validations . Map.lookup "eyr",
hgt validations . Map.lookup "hgt",
hcl validations . Map.lookup "hcl",
ecl validations . Map.lookup "ecl",
pid validations . Map.lookup "pid"
]
loosePassportValidations :: Validations
loosePassportValidations =
Validations
{ byr = isJust,
iyr = isJust,
eyr = isJust,
hgt = isJust,
hcl = isJust,
ecl = isJust,
pid = isJust
}
strictPassportValidations :: Validations
strictPassportValidations =
Validations
{ byr = maybe False (isBetween 1920 2002 . readInt),
iyr = maybe False (isBetween 2010 2020 . readInt),
eyr = maybe False (isBetween 2020 2030 . readInt),
hgt = maybe False validHgt,
hcl = maybe False validHcl,
ecl = maybe False (`elem` ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]),
pid = maybe False validPid
}
where
validHgt :: String -> Bool
validHgt = isRight . parse (hgtParser <* eof) ""
hgtParser :: Parsec String st String
hgtParser = try (rangeParser 150 193 <* string "cm") <|> (rangeParser 59 76 <* string "in")
validHcl :: String -> Bool
validHcl = isRight . parse (char '#' *> count 6 hexDigit <* eof) ""
validPid :: String -> Bool
validPid = isRight . parse (count 9 digit <* eof) ""
rangeParser :: Int -> Int -> Parsec String st String
rangeParser lo hi = do
value <- many1 digit
if isBetween lo hi (readInt value)
then pure value
else unexpected ("value " ++ value ++ ", expected a value between " ++ show lo ++ " and " ++ show hi)