Add cl-interpol
[adventofcode2020.git] / src / day04.lisp
1 (asdf:load-system :adventofcode2020)
2 (in-package #:adventofcode2020)
3 (named-readtables:in-readtable :adventofcode2020)
4
5 (defun parse-passport (str-list)
6 (flet ((parser (str)
7 (->> (split-sequence #\Space str)
8 (mapcar (fn* (destructuring-bind (a b) (split-sequence #\: _)
9 (list (read-from-string a) b)))))))
10 (mapcan #'parser str-list)))
11
12 (defun four-digits-test (pred)
13 (lambda (s)
14 (cl-ppcre:register-groups-bind
15 ((#'parse-integer value)) ("^([0-9]{4})$" s)
16 (funcall pred value))))
17
18 (defparameter *required-field-tests*
19 (list 'byr (four-digits-test λ(<= 1920 _ 2002))
20 'iyr (four-digits-test λ(<= 2010 _ 2020))
21 'eyr (four-digits-test λ(<= 2020 _ 2030))
22 'hgt λ(cl-ppcre:register-groups-bind
23 ((#'parse-integer value) unit) ("^([0-9]+)(in|cm)$" _)
24 (cond
25 ((string= unit "in") (<= 59 value 76))
26 ((string= unit "cm") (<= 150 value 193))
27 (t nil)))
28 'hcl λ(cl-ppcre:scan "^#[0-9a-f]{6}$" _)
29 'ecl λ(cl-ppcre:scan "^(amb|blu|brn|gry|grn|hzl|oth)$" _)
30 'pid λ(cl-ppcre:scan "^[0-9]{9}$" _)
31 'cid (constantly t)))
32
33 (defun simple-validate-passport (passport)
34 (let ((required-fields '(byr iyr eyr hgt hcl ecl pid)))
35 (every #'identity (mapcar λ(assoc _ passport) required-fields))))
36
37 (defun complex-validate-passport (passport)
38 (flet ((check (pair)
39 (destructuring-bind (field value) pair
40 (funcall (getf *required-field-tests* field) value))))
41 (and
42 (simple-validate-passport passport)
43 (every #'identity (mapcar #'check passport)))))
44
45 (day 04 input
46 (let ((passports (-<>> (list-from input)
47 (split-sequence "" <> :test #'string=)
48 (mapcar #'parse-passport))))
49 (part1 (count t (mapcar #'simple-validate-passport passports)))
50 (part2 (count t (mapcar #'complex-validate-passport passports)))))
51
52 (def-suite day04)
53 (in-suite day04)
54
55 (defvar *passports*
56 '(("ecl:gry pid:860033327 eyr:2020 hcl:#fffffd"
57 "byr:1937 iyr:2017 cid:147 hgt:183cm")
58 ("iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884"
59 "hcl:#cfa07d byr:1929")
60 ("hcl:#ae17e1 iyr:2013" "eyr:2024"
61 "ecl:brn pid:760753108 byr:1931" "hgt:179cm")
62 ("hcl:#cfa07d eyr:2025 pid:166559648"
63 "iyr:2011 ecl:brn hgt:59in")))
64
65 (test parse-passports
66 (is (equal
67 '(((ecl "gry") (pid "860033327") (eyr "2020") (hcl "#fffffd") (byr "1937")
68 (iyr "2017") (cid "147") (hgt "183cm"))
69 ((iyr "2013") (ecl "amb") (cid "350") (eyr "2023") (pid "028048884")
70 (hcl "#cfa07d") (byr "1929"))
71 ((hcl "#ae17e1") (iyr "2013") (eyr "2024") (ecl "brn") (pid "760753108")
72 (byr "1931") (hgt "179cm"))
73 ((hcl "#cfa07d") (eyr "2025") (pid "166559648") (iyr "2011") (ecl "brn")
74 (hgt "59in")))
75 (mapcar #'parse-passport *passports*))))
76
77 (test simple-validate-passports
78 (is (equal
79 '(t nil t nil)
80 (mapcar (compose #'simple-validate-passport #'parse-passport) *passports*))))
81
82 (test passport-field-validators
83 (flet ((check (pair)
84 (destructuring-bind (field value) pair
85 (funcall (getf *required-field-tests* field) value))))
86 (is-true (check '(byr "2002")))
87 (is-false (check '(byr "2003")))
88
89 (is-true (check '(hgt "60in")))
90 (is-true (check '(hgt "190cm")))
91 (is-false (check '(hgt "190in")))
92 (is-false (check '(hgt "190")))
93
94 (is-true (check '(hcl "#123abc")))
95 (is-false (check '(hcl "#123abz")))
96 (is-false (check '(hcl "123abc")))
97
98 (is-true (check '(ecl "brn")))
99 (is-false (check '(ecl "wat")))
100
101 (is-true (check '(pid "000000001")))
102 (is-false (check '(pid "0123456789")))))
103
104 (defvar *valid-passports*
105 '(((pid "087499704") (hgt "74in") (ecl "grn") (iyr "2012") (eyr "2030")
106 (byr "1980") (hcl "#623a2f"))
107 ((eyr "2029") (ecl "blu") (cid "129") (byr "1989") (iyr "2014")
108 (pid "896056539") (hcl "#a97842") (hgt "165cm"))
109 ((hcl "#888785") (hgt "164cm") (byr "2001") (iyr "2015") (cid "88")
110 (pid "545766238") (ecl "hzl") (eyr "2022"))
111 ((iyr "2010") (hgt "158cm") (hcl "#b6652a") (ecl "blu") (byr "1944")
112 (eyr "2021") (pid "093154719"))))
113
114 (defvar *invalid-passports*
115 '(((eyr "1972") (cid "100") (hcl "#18171d") (ecl "amb") (hgt "170")
116 (pid "186cm") (iyr "2018") (byr "1926"))
117 ((iyr "2019") (hcl "#602927") (eyr "1967") (hgt "170cm") (ecl "grn")
118 (pid "012533040") (byr "1946"))
119 ((hcl "dab227") (iyr "2012") (ecl "brn") (hgt "182cm") (pid "021572410")
120 (eyr "2020") (byr "1992") (cid "277"))
121 ((hgt "59cm") (ecl "zzz") (eyr "2038") (hcl "74454a") (iyr "2023")
122 (pid "3556412378") (byr "2007"))))
123
124 (test complex-validate-passports
125 (loop for passport in *valid-passports*
126 do (is-true (complex-validate-passport passport)))
127 (loop for passport in *invalid-passports*
128 do (is-false (complex-validate-passport passport))))
129
130 (run! 'day04)