If you have a lisp installation, emacs, org-mode, and org-babel support for lisp installed you can run this by:
- Starting slime (
M-x slime
) - Typing
C-c C-c
in the block initialize. - In the repl type
(in-package :aoc-2020-04)
- Typing
C-c C-c
in the block answers
(unless (find-package :cl-ppcre)
(ql:quickload "cl-ppcre"))
(unless (find-package :iterate)
(ql:quickload "iterate"))
(unless (find-package :parseq)
(ql:quickload "parseq"))
(unless (find-package :fiveam)
(ql:quickload "fiveam"))
(unless (find-package :series)
(ql:quickload "series"))
(unless (find-package :cl-permutation)
(ql:quickload "cl-permutation"))
(unless (find-package :bordeaux-threads)
(ql:quickload "bordeaux-threads"))
<<packages>>
(defpackage :aoc-2020-04
(:use :common-lisp
:iterate
:parseq
:fiveam)
(:export :problem-a
:problem-b))
(in-package :aoc-2020-04)
(defun gather-fields (lines)
(loop for l in lines
with result = nil
with entry = ""
do (cond ((string= "" l)
(push entry result)
(setf entry ""))
(t (setf entry (concatenate 'string entry " " l))))
finally (push entry result)
(return result)))
(defun read-input (file)
(gather-fields
(iter (for line in-file file using #'read-line)
(collect line))))
(defparameter *input*
(read-input "input/04.txt"))
(defun validate-passport (passport)
(let ((needed '("byr" "hgt" "iyr" "eyr" "hcl" "ecl" "pid")))
(every (lambda (field) (cl-ppcre:scan field passport)) needed)))
<<validate-passport>>
(defun problem-a () (format t "Problem 04 A: ~a~%" (count-if #'validate-passport *input*)))
I could spend some time and fix up my parser to split out each
part. But I won’t. I’ll split it using cl-ppcre
on spaces, then scan
each one and add a validator for each field prefix.
(defun get-field (field entry)
(assoc field entry :test #'string=))
(defun validate-byr (field)
(and field
(cl-ppcre:scan "[0-9]{4}" (second field))
(<= 1920 (parse-integer (second field)) 2002)))
(defun validate-iyr (field)
(and field
(cl-ppcre:scan "[0-9]{4}" (second field))
(<= 2010 (parse-integer (second field)) 2020)))
(defun validate-eyr (field)
(and field
(cl-ppcre:scan "[0-9]{4}" (second field))
(<= 2020 (parse-integer (second field)) 2030)))
(defun validate-hgt (field)
(and field
(let ((s (second field)))
(or (and (string= "cm" (subseq s (- (length s) 2)))
(<= 150 (parse-integer s :junk-allowed t) 193))
(and (string= "in" (subseq s (- (length s) 2)))
(<= 59 (parse-integer s :junk-allowed t) 76))))))
(defun validate-hcl (field)
(and field
(cl-ppcre:scan "#[0-9a-f]{6}" (second field))))
(defun validate-ecl (field)
(and field
(some (lambda (c) (string= c (second field)))
'("amb" "blu" "brn" "gry" "grn" "hzl" "oth"))))
(defun validate-pid (field)
(and field
(cl-ppcre:scan "^[0-9]{9}$" (second field))))
(defun properly-validate (entry)
(let ((fields (mapcar (lambda (e) (cl-ppcre:split ":" e))
(rest (cl-ppcre:split " " entry)))))
(and (<= 7 (length fields) 8)
(validate-byr (get-field "byr" fields))
(validate-iyr (get-field "iyr" fields))
(validate-eyr (get-field "eyr" fields))
(validate-hgt (get-field "hgt" fields))
(validate-hcl (get-field "hcl" fields))
(validate-ecl (get-field "ecl" fields))
(validate-pid (get-field "pid" fields)))))
<<properly-validate>>
(defun problem-b () (format t "Problem 04 B: ~a~%" (count-if #'properly-validate *input*)))
<<read-input>>
<<input>>
<<initialize>>
<<structs>>
<<functions>>
<<input>>
<<problem-a>>
<<problem-b>>
(problem-a)
(problem-b)
(def-suite aoc.2020.04)
(in-suite aoc.2020.04)
(run! 'aoc.2020.04)
Well, part 2 was a pain because of a stupid error. At least I won’t make that same mistake here.
with AOC2020.Day04;
procedure Day04 is
begin
AOC2020.Day04.Run;
end Day04;
Specification for solution.
package AOC2020.Day04 is
procedure Run;
end AOC2020.Day04;
Actual implementation body.
with GNAT.Regpat; use GNAT.Regpat;
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
with Ada.Containers.Ordered_Maps;
with Text_IO; use Text_IO;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
package body AOC2020.Day04 is
subtype Birth_Year is Integer range 1920..2002;
subtype Issue_Year is Integer range 2010..2020;
subtype Expiration_Year is Integer range 2020..2030;
subtype Digit is Character range '0' .. '9';
subtype Hex_Letter is Character range 'a' .. 'f';
package Passport_Maps is new Ada.Containers.Ordered_Maps
(Key_Type => Unbounded_String, Element_Type => Unbounded_String);
use Passport_Maps;
package Passport_Vectors is new Ada.Containers.Vectors
(Index_Type => Positive, Element_Type => Map);
use Passport_Vectors;
procedure Parse(Passports : out Vector) is
F : File_Type;
Passport : Map;
begin
Open(F, In_File, "../input/04.txt");
loop
exit when End_Of_File(F);
declare
Line : constant String := Get_Line(F);
I : Positive := 1;
Field_Name : Unbounded_String;
Field_Value : Unbounded_String;
begin
if Line'Length = 0
then
Passports.Append(Passport.Copy);
Passport.Clear;
else
I := 1;
for C in Line'Range loop
if Line(C) = ':'
then
Field_Name := To_Unbounded_String(Line(I..C-1));
I := C + 1;
elsif Line(C) = ' '
then
Field_Value := To_Unbounded_String(Line(I..C-1));
I := C + 1;
Passport.Insert(Field_Name, Field_Value);
end if;
end loop;
Field_Value := To_Unbounded_String(Line(I..Line'Last));
Passport.Insert(Field_Name, Field_Value);
end if;
end;
end loop;
Passports.Append(Passport);
Close(F);
end Parse;
function Has_Mandatory_Fields(Passport : Map) return Boolean is
Valid : Boolean := True;
Keys : array (1..7) of String(1..3) := ("byr", "eyr", "iyr", "hgt", "ecl", "hcl", "pid");
begin
for K of Keys loop
Valid := Valid and Contains(Passport,To_Unbounded_String(K));
end loop;
return Valid;
end Has_Mandatory_Fields;
function Has_Valid_Birth_Year(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("byr");
begin
return Passport.Contains(Key)
and then Integer'Value(To_String(Passport.Element(Key))) in Birth_Year;
end Has_Valid_Birth_Year;
function Has_Valid_Issue_Year(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("iyr");
begin
return Passport.Contains(Key)
and then Integer'Value(To_String(Passport.Element(Key))) in Issue_Year;
end Has_Valid_Issue_Year;
function Has_Valid_Expiration_Year(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("eyr");
begin
return Passport.Contains(Key)
and then Integer'Value(To_String(Passport.Element(Key))) in Expiration_Year;
end Has_Valid_Expiration_Year;
function Has_Valid_Hair_Color(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("hcl");
begin
if Passport.Contains(Key)
then
declare
S : String := To_String(Passport.Element(Key));
begin
if S'Length /= 7 then return False; end if;
if S(1) /= '#' then return False; end if;
for C of S(2..7) loop
if C not in Digit and C not in Hex_Letter
then return False;
end if;
end loop;
end;
else
return False;
end if;
return True;
end Has_Valid_Hair_Color;
function Has_Valid_Eye_Color(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("ecl");
Valid_Hair : array (1..7) of String(1..3) := ("amb", "blu", "brn", "gry", "grn", "hzl", "oth");
Valid : Boolean := False;
begin
if Passport.Contains(Key)
then
declare
S : String := To_String(Passport.Element(Key));
begin
if S'Length /= 3 then return False; end if;
for Color of Valid_hair loop
Valid := Valid or Color = S;
end loop;
end;
else
return False;
end if;
return Valid;
end Has_Valid_Eye_Color;
function Has_Valid_PID(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("pid");
begin
if Passport.Contains(Key)
then
declare
S : String := To_String(Passport.Element(Key));
begin
if S'Length /= 9 then return False; end if;
for C of S loop
if C not in Digit
then return False;
end if;
end loop;
end;
else
return False;
end if;
return True;
end Has_Valid_PID;
function Has_Valid_Height(Passport : Map) return Boolean is
Key : Unbounded_String := To_Unbounded_String("hgt");
begin
if Passport.Contains(Key)
then
declare
S : String := To_String(Passport.Element(Key));
H : Natural;
begin
if S'Length < 4 then return False; end if;
for C in 1..S'Last-2 loop
if S(C) not in Digit
then return False;
end if;
end loop;
H := Integer'Value(S(1..S'Last-2));
if S(S'Last-1..S'Last) = "cm"
then
return H in 150..193;
elsif S(S'Last-1..S'Last) = "in"
then
return H in 59..76;
end if;
end;
else
return False;
end if;
return False;
end Has_Valid_Height;
function Has_Valid_Fields(Passport : Map) return Boolean is
begin
return Has_Valid_Birth_Year(Passport) and Has_Valid_Issue_Year(Passport)
and Has_Valid_Expiration_Year(Passport) and Has_Valid_Hair_Color(Passport)
and Has_Valid_Eye_Color(Passport) and Has_Valid_PID(Passport)
and Has_Valid_Height(Passport);
end Has_Valid_Fields;
procedure Run is
Passports : Vector;
Valid : Natural;
begin
Parse (Passports);
Put_Line("Advent of Code 2020 - Day 04:"); New_Line;
Valid := 0;
for C in Passports.Iterate loop
if Has_Mandatory_Fields(Passports(C))
then
Valid := Valid + 1;
end if;
end loop;
Put_Line("The result for part 1 is: " & Valid'Image);
Valid := 0;
for C in Passports.Iterate loop
if Has_Valid_Fields(Passports(C))
then
Valid := Valid + 1;
end if;
end loop;
Put_Line("The result for Part 2 is: " & Valid'Image);
end Run;
end AOC2020.Day04;
In order to run this you have to “tangle” the code first using C-c
C-v C-t
.
cd ada
gnatmake day04
./day04