Skip to content

Files

Latest commit

51a78c0 · Dec 17, 2020

History

History
343 lines (323 loc) · 11.3 KB

2020.17.org

File metadata and controls

343 lines (323 loc) · 11.3 KB

Day 17

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-17)
  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-17
  (:use :common-lisp
        :iterate
        :parseq
        :fiveam)
  (:export :problem-a
           :problem-b))
(in-package :aoc-2020-17)

Input

(defun make-grids (lines)
  (loop for l in lines
     with 3d-grid = (make-hash-table :test 'equal)
     with 4d-grid = (make-hash-table :test 'equal)
     for y from 0
     with z = 0
     with w = 0
     do (loop for c across l
           for x from 0
           if (char= #\# c)
           do (setf (gethash (list x y z) 3d-grid) c)
             (setf (gethash (list x y z w) 4d-grid) c))
     finally (return (list 3d-grid 4d-grid))))
(defun read-input (file)
  (make-grids
   (iter (for line in-file file using #'read-line)
         (collect line))))
(defparameter *input*
  (read-input "input/17.txt"))

Part 1

Another GoL style puzzle. This time it operates in 3d, though our input is for just one slice. The space is unbounded, that is we can’t just stop examining at the edge it’s possible to expand beyond the current known area.

Two rules:

  • If a cube is active, then it remains active if 2 or 3 of its neighbors are also active. Otherwise it becomes inactive.
  • If a cube is inactive but exactly 3 of its neighbors are active, it becomes active. Otherwise it remains inactive.

Since both rules depend on being adjacent to other cubes I’m going to use a sparse representation. Only active cubes will be used. Their neighbors (all 26 of them) will be queued up for examination.

(defun count-neighbors (grid location)
  (destructuring-bind (x y z) location
    (loop for i from (1- x) to (1+ x)
       sum (loop for j from (1- y) to (1+ y)
              sum (loop for k from (1- z) to (1+ z)
                     if (not (and (= i x) (= j y) (= k z)))
                     count (char= #\# (gethash (list i j k) grid #\.)))))))

(defun neighbors (location)
  (destructuring-bind (x y z) location
    (loop for i from (1- x) to (1+ x)
       with neighbors = nil
       finally (return neighbors)
       do (loop for j from (1- y) to (1+ y)
             do (loop for k from (1- z) to (1+ z)
                   if (not (and (= i x) (= j y) (= k z)))
                   do (pushnew (list i j k) neighbors :test #'equal))))))

(defun active-rule (grid location)
  (if (<= 2 (count-neighbors grid location) 3)
      #\#
      #\.))

(defun inactive-rule (grid location)
  (if (= 3 (count-neighbors grid location))
      #\#
      #\.))

(defun next-step (grid)
  (let ((next (make-hash-table :test 'equal))
        (to-check nil))
    (loop
       for location being the hash-keys of grid
       do (pushnew location to-check :test #'equal)
         (setf to-check (union to-check (neighbors location))))
    (loop for location in to-check
       for delta = (case (gethash location grid #\.)
                     (#\# (active-rule grid location))
                     (#\. (inactive-rule grid location)))
       if (char= delta #\#)
       do (setf (gethash location next) #\#))
    next))

(defun solve-a (grid)
  (loop repeat 6
     with grid = grid
     do (setf grid (next-step grid))
     finally (return (hash-table-count grid))))
(defun problem-a () (format t "Problem 17 A: ~a~%" (solve-a (first *input*))))

Part 2

I’m literally just going to copy the logic from above and make a 4d version

(defun count-neighbors-4d (grid location)
  (destructuring-bind (x y z w) location
    (loop for i from (1- x) to (1+ x)
       sum (loop for j from (1- y) to (1+ y)
              sum (loop for k from (1- z) to (1+ z)
                     sum (loop for l from (1- w) to (1+ w)
                            if (not (and (= i x) (= j y) (= k z) (= l w)))
                            count (char= #\# (gethash (list i j k l) grid #\.))))))))

(defun neighbors-4d (location)
  (destructuring-bind (x y z w) location
    (loop for i from (1- x) to (1+ x)
       with neighbors = nil
       finally (return neighbors)
       do (loop for j from (1- y) to (1+ y)
             do (loop for k from (1- z) to (1+ z)
                   do (loop for l from (1- w) to (1+ w)
                         if (not (and (= i x) (= j y) (= k z) (= l w)))
                         do (pushnew (list i j k l) neighbors :test #'equal)))))))

(defun active-rule-4d (grid location)
  (if (<= 2 (count-neighbors-4d grid location) 3)
      #\#
      #\.))

(defun inactive-rule-4d (grid location)
  (if (= 3 (count-neighbors-4d grid location))
      #\#
      #\.))

(defun next-step-4d (grid)
  (let ((next (make-hash-table :test 'equal))
        (to-check nil))
    (loop
       for location being the hash-keys of grid
       do (push location to-check); :test #'equal)
         (setf to-check (append to-check (neighbors-4d location))))
    (loop for location in to-check
       for delta = (case (gethash location grid #\.)
                     (#\# (active-rule-4d grid location))
                     (#\. (inactive-rule-4d grid location)))
       if (char= delta #\#)
       do (setf (gethash location next) #\#))
    next))

(defun next-step-4d-bb (grid)
  (let ((next (make-hash-table :test 'equal)))
    (destructuring-bind (min-x min-y min-z min-w
                               max-x max-y max-z max-w)
        (loop
           for (x y z w) being the hash-keys of grid
           finally (return (list (1- min-x) (1- min-y) (1- min-z) (1- min-w)
                                 (1+ max-x) (1+ max-y) (1+ max-z) (1+ max-w)))
           minimizing x into min-x
           minimizing y into min-y
           minimizing z into min-z
           minimizing w into min-w
           maximizing x into max-x
           maximizing y into max-y
           maximizing z into max-z
           maximizing w into max-w)
      (loop for x from min-x to max-x
         do (loop for y from min-y to max-y
               do (loop for z from min-z to max-z
                     do (loop for w from min-w to max-w
                           for location = (list x y z w)
                             for delta = (case (gethash location grid #\.)
                                           (#\# (active-rule-4d grid location))
                                           (#\. (inactive-rule-4d grid location)))
                           if (char= delta #\#)
                           do (setf (gethash location next) #\#)))))
      next)))

(defun solve-b (grid)
  (loop repeat 6
     with grid = grid
     do (setf grid (next-step-4d-bb grid))
     finally (return (hash-table-count grid))))

Of course, that takes 30 seconds. I could definitely speed it up, all the union stuffs take a lot of time. It may actually be faster to just append the results. Going to try it real quick. Much faster, about 4 seconds. But what we really want is to find a bounding box which can be calculated in one pass. Less than one second. Not bad, a 30x improvemnet ubt makes the code a bit uglier (all the minimziing/maximizing).

(defun problem-b () (format t "Problem 17 B: ~a~%" (solve-b (second *input*))))

Putting it all together

<<read-input>>
<<input>>
<<conway-cube-step>>
<<conway-cube-4d>>
<<initialize>>
<<structs>>
<<functions>>
<<input>>
<<problem-a>>
<<problem-b>>
(problem-a)
(problem-b)

Answer

Problem 17 A: 237
Problem 17 B: 2448

Test Cases

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

(run! 'aoc.2020.17)

Test Results

Thoughts

Ada

Runner

Simple runner.

with AOC2020.Day17;
procedure Day17 is
begin
  AOC2020.Day17.Run;
end Day17;

Specification

Specification for solution.

package AOC2020.Day17 is
   procedure Run;
end AOC2020.Day17;

Packages

with GNAT.Regpat; use GNAT.Regpat;
with Text_IO; use Text_IO;

Types and generics

Implementation

Actual implementation body.

<<ada-packages>>
package body AOC2020.Day17 is
   <<types-and-generics>>
   -- Used as an example of matching regular expressions
   procedure Parse_Line (Line : Unbounded_String; P : out Password) is
      Pattern : constant String := "(\d+)-(\d+) ([a-z]): ([a-z]+)";
      Re : constant Pattern_Matcher := Compile(Pattern);
      Matches : Match_Array (0..4);
      Pass : Unbounded_String;
      P0, P1 : Positive;
      C : Character;
   begin
      Match(Re, To_String(Line), Matches);
      P0 := Integer'Value(Slice(Line, Matches(1).First, Matches(1).Last));
      P1 := Integer'Value(Slice(Line, Matches(2).First, Matches(2).Last));
      C := Element(Line, Matches(3).First);
      Pass := To_Unbounded_String(Slice(Line, Matches(4).First, Matches(4).Last));
      P := (Min_Or_Pos => P0,
            Max_Or_Pos => P1,
            C => C,
            P => Pass);
   end Parse_Line;
   procedure Run is
   begin
      Put_Line("Advent of Code 2020 - Day 17");
      Put_Line("The result for Part 1 is " & Integer'Image(0));
      Put_Line("The result for Part 2 is " & Integer'Image(0));
   end Run;
end AOC2020.Day17;

Run the program

In order to run this you have to “tangle” the code first using C-c C-v C-t.

cd ada
gnatmake day17
./day17