src/2019/days/day-10.lisp @ 182bdd87fd9e

Refactor, remove quickutil
author Steve Losh <steve@stevelosh.com>
date Mon, 29 Nov 2021 23:19:14 -0500
parents ebd2a1bb4889
children (none)
(advent:defpackage* :advent/2019/10)
(in-package :advent/2019/10)



(defun equivalence-classes (equiv seq) ; From quickutil TODO replace this 
  "Partition the sequence `seq` into a list of equivalence classes
defined by the equivalence relation `equiv`."
  (let ((classes nil))
    (labels ((find-equivalence-class (x)
               (member-if (lambda (class)
                            (funcall equiv x (car class)))
                          classes))

             (add-to-class (x)
               (let ((class (find-equivalence-class x)))
                 (if class
                   (push x (car class))
                   (push (list x) classes)))))
      (declare (dynamic-extent (function find-equivalence-class)
                               (function add-to-class))
               (inline find-equivalence-class
                       add-to-class))

      ;; Partition into equivalence classes.
      (map nil #'add-to-class seq)

      ;; Return the classes.
      classes)))

(defun asteroid-positions (map)
  "Return a list of the asteroid positions in the 2D input `map`."
  (destructuring-bind (rows cols) (array-dimensions map)
    (iterate
      (for-nested ((row :from 0 :below rows)
                   (col :from 0 :below cols)))
      (when (char= #\# (aref map row col))
        ;; swap axes to match the problem text
        (collect (complex col row))))))

(defun slope (v)
  (let-complex (v)
    (if (zerop vy)
      nil
      (/ vx vy))))

(defun slope= (a b)
  (eql (slope a)
       (slope b)))

(defun sign= (a b)
  (and (= (signum (x a)) (signum (x b)))
       (= (signum (y a)) (signum (y b)))))

(defun colinearp (origin a b)
  (let ((va (- a origin))
        (vb (- b origin)))
    (and (sign= va vb)
         (slope= va vb))))

(defun group (asteroids origin)
  (equivalence-classes (curry #'colinearp origin)
                       (remove origin asteroids)))

(defun count-seen (asteroids pos)
  (length (group asteroids pos)))

(defun angle (a b)
  (mod (- (atan (y a) (x a))
          (atan (y b) (x b)))
       tau))

(defun distance (a b)
  (abs (- a b)))

(defun splice (list)
  "Splice `list` into a circular list."
  (setf (cdr (last list)) list))

(defun part1 (data)
  (iterate
    (with asteroids = (asteroid-positions data))
    (for pos :in asteroids)
    (finding pos :maximizing (count-seen asteroids pos) :into (best score))
    (returning best score)))

(defun part2 (data origin &optional (n 200))
  (flet ((group-angle (group)
           (angle #c(0 -1) (- (first group) origin)))
         (sort-group (group)
           (sort group #'< :key (curry #'distance origin))))
    (iterate
      (with groups = (_ data
                       asteroid-positions
                       (group _ origin)
                       (mapcar #'sort-group _)
                       (sort _ #'< :key #'group-angle)
                       (apply #'ring _)))
      (repeat n)
      (for pos = (pop (ring-data groups)))
      (if (null (ring-data groups))
        ;; We go in the opposite direction than expected because the Y axis is
        ;; annoyingly flipped in the problem.
        (ring-cutf groups :prev t)
        (ring-prevf groups))
      (returning pos))))

(define-problem (2019 10) (data read-2d-array) (284 404)
  (multiple-value-bind (origin score) (part1 data)
    (let ((lucky (part2 data origin)))
      (values score (+ (* (x lucky) 100) (y lucky))))))


#; Scratch --------------------------------------------------------------------