Skip to content

Latest commit

 

History

History
412 lines (396 loc) · 13.2 KB

2020.04.org

File metadata and controls

412 lines (396 loc) · 13.2 KB

Day 04

Executing this code

If you have a lisp installation, emacs, org-mode, and org-babel support for lisp installed you can run this by:

  1. Starting slime (M-x slime)
  2. Typing C-c C-c in the block initialize.
  3. In the repl type (in-package :aoc-2020-04)
  4. Typing C-c C-c in the block answers

Initial stuffs

Packages to load

(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"))

Create package for this day

<<packages>>
(defpackage :aoc-2020-04
  (:use :common-lisp
        :iterate
        :parseq
        :fiveam)
  (:export :problem-a
           :problem-b))
(in-package :aoc-2020-04)

Input

(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"))

Part 1

(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*)))

Part 2

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*)))

Putting it all together

<<read-input>>
<<input>>
<<initialize>>
<<structs>>
<<functions>>
<<input>>
<<problem-a>>
<<problem-b>>
(problem-a)
(problem-b)

Answer

Test Cases

(def-suite aoc.2020.04)
(in-suite aoc.2020.04)

(run! 'aoc.2020.04)

Test Results

Thoughts

Ada

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