ADVENT OF CODE

It's the holiday season! This year I'm trying my hand at a few
puzzles. I'm also giving away lots of holiday cards that I design and
printed myself (image below). All in all, it's a nice time of the year
to strive for a bit more challenge in my intellectual pursuits, and
happiness in my friendships.

Day 1
----------------------------------------------------------------------
What I learned: getting the line count of a file and storing it in a
variable doesn't work. It seems that a separate shell process a is
spawned from this assignment, and so the code will carry on with its
evaluation without waiting for the result. This was solved by moving
the process that reads the value into the `for' loop.

,----
|   PWD=$PWD
|   INPUT="data.txt"
|   BUFFER_DIR="$PWD/buffer"
|   # LINES=$(wc -l $INPUT | grep -Po "[0-9]*$") This dosn't work
|   rm count.temp
|   wc -l $INPUT | grep -Po "[0-9]*" >> count.temp
| 
|   rm -r $BUFFER_DIR
|   mkdir $BUFFER_DIR
| 
|   INDEX=0
|   CALS_FOR_THIS_ELF=0
|   # Need to count lines inside this statement, otherwise it don't work!
|   for (( INDEX=0; INDEX < $(cat count.temp); INDEX++)); do
|       ITEM_CALS=$(tail -n $INDEX $INPUT | head -n 1)
|       IS_EMPTY_LINE=$(echo $ITEM_CALS | grep -Pc "^[[:space:]]*$")
| 
|       if [[ IS_EMPTY_LINE -gt 0 ]]; then
|           touch "$BUFFER_DIR/$CALS_FOR_THIS_ELF"
|           CALS_FOR_THIS_ELF=0
|       fi
| 
|       CALS_FOR_THIS_ELF=$(( CALS_FOR_THIS_ELF + ITEM_CALS ))
|   done
| 
|   cd $BUFFER_DIR
|   ls -1 * | sort -Vr | head -n 3
|   cd $PWD
`----

Day 2
----------------------------------------------------------------------
I wanted something easy for my second day doing the Advent of Code, so
I settled on using Lua.

,----
|   local SCORE_TABLE = {
|      X = 1,
|      Y = 2,
|      Z = 3
|   }
| 
|   local WIN_TABLE = {
|      A = "Z", -- Rock beats sissors
|      B = "X", -- Paper beats rock
|      C = "Y"  -- Sissors beats paper
|   }
| 
|   local DRAW_TABLE = {
|      A = "X",
|      B = "Y",
|      C = "Z",
|   }
| 
|   function round_info(elf, me)
|      local info
|      info = {
|         score = function()
|            if info.drawer() then
|               return 3 + SCORE_TABLE[me]
|            elseif info.winner() then
|               return 6 + SCORE_TABLE[me]
|            else
|               return SCORE_TABLE[me]
|            end
|         end,
|         winner = function()
|            return (WIN_TABLE[elf] ~= me)
|         end,
|         drawer = function()
|            return (DRAW_TABLE[elf] == me)
|         end,
|      }
|      return info
|   end
| 
|   local read_file = function (path)
|      local file = io.open(path, "rb")
|      local rows = {}
|      for col in io.lines(path) do
|         local row = {}
|         for x in col:gmatch("%w+") do
|            table.insert(row, x)
|         end
|         table.insert(rows, round_info(row[1],row[2]))
|      end
|      file:close()
|      return rows;
|   end
| 
|   local my_score = 0
|   for _, row in ipairs(read_file("data.txt")) do
|      my_score = my_score + row.score()
|   end
| 
|   print(my_score)
`----


Day 3
----------------------------------------------------------------------
This was a great way for me to apply the LISP I've been reading.

I learned mostly about helpful string methods: splitting string,
converting a string to a char. Basic string stuff that's always good
to know in a language! I'm feeling more confident about EmacsLisp. The
use of recursion is delightful. Much more enjoyable than using a
loop. More powerful, too, I think, because the act of recursion,
paired with an accumulator, lets one dataset be built while another is
being deconstructed. Cool! I am a LISP gal now.

,----
|   (setq max-specpdl-size 32000)
|   (reduce #'+ (bagger (split-string (file-contents "./data.txt") "\n" t) (list)))
| 
|   (defun bagger(bag accumulator)
|     (if (null bag)
|         accumulator
|       (let* ((contents (car bag))
|              (len (cdr (read-from-string contents)))
|              (front-compartment (substring contents 0 (/ len 2)))
|              (back-compartment (substring contents (/ len 2) len)))
|         (setq accumulator
|               (append (list (char-to-priority (car (find-matching-chars front-compartment back-compartment)))) accumulator))
|         (bagger (cdr bag) accumulator))))
| 
|   (defun char-to-priority (char)
|     (let ((charnum (string-to-char char)))
|       (if (s-uppercase-p char)
|           (upper-char-to-priority charnum)
|         (lower-char-to-priority charnum))))
| 
|   (defun upper-char-to-priority (char)
|     (- char 38))
| 
|   (defun lower-char-to-priority (char)
|     (- char 96))
| 
|   (defun find-matching-chars (stringa stringb)
|     (let ((matched-chars "")
|           (stringa (split-string stringa "\\|[a-zA-Z]+" t))
|           (stringb (split-string stringb "\\|[a-zA-Z]+" t)))
|       (split-string (mapconcat (lambda (x) (char-in-string x stringb)) stringa "")
|                     "\\|[a-zA-Z]+" t)))
| 
|   (defun char-in-string (char list)
|     (cond ((null list) nil)
|           ((string= char (car list)) char)
|           (t (char-in-string char (cdr list)))))
| 
|   (defun file-contents (filename)
|     (with-temp-buffer
|       (insert-file-contents filename)
|       (buffer-string)))
`----


Day 4
----------------------------------------------------------------------
I really wanted to use regex for matching the data, but couldn't find
great documentation around moving through matched strings. So I used
the `split-string' function, as before, to parse each row of data into
the numbers I needed.

I also boxed myself into a corner by doing something weird early on:
converting the range from its boundaries (e.g: ("1" "6")) into a list
of each value therein (i.e.: (1 2 3 4 5 6)). Anyways, turns out it's
possible to check if one list of values is entirely contained within
another by comparing the length of the intersection with the length of
both lists.

,----
|   (defun file-contents (filename)
|     (with-temp-buffer
|       (insert-file-contents filename)
|       (buffer-string)))
| 
|   (defun int-range-to-list(start end accumulator)
|     ;; given a range of numbers, produce a list
|     (if (> start end)
|         accumulator
|       (setq accumulator (append (list start) accumulator))
|       (int-range-to-list (+ 1 start) end accumulator)
|       ))
| 
|   (defun unwrap-elves(elves)
|   (mapcar (lambda (elf-pair)
|             (let* ((cleaning-zones-boundaries (mapcar (lambda (elf)
|                                                         (split-string elf "-" t))
|                                                       (split-string elf-pair "," t)))
|                    (cleaning-zones-range (mapcar (lambda (zone)
|                                                    (int-range-to-list (string-to-number (car zone)) (string-to-number (cadr zone)) (list)))
|                                                  cleaning-zones-boundaries))
|                    (range-a (car cleaning-zones-range))
|                    (range-b (cadr cleaning-zones-range))
|                    (overlap (intersection range-a range-b))
|                    ;; part 1
|                    (is-contained (cond ((and (> (length overlap) 0)
|                                              (or (= (length range-a) (length overlap))
|                                                  (= (length range-b) (length overlap)))) 1)
|                                        (t 0)))
|                    ;; part 2
|                    (all-contained (cond ((> (length overlap) 0) 1)
|                                         (t 0)))
|                    )
|               all-contained
|               )) elves))
| 
|   (reduce #'+(unwrap-elves (split-string (file-contents "./data.txt") "\n" t)))
`----


Day 5
----------------------------------------------------------------------
Today was punishing. The input data included a visual representation
of a table that needed to be parsed for the letters it contained. The
letters needed to include column info, and this proved to be the most
challenging information to extract. I had to create a function
`translate' that took the location of the letter and translated that
number to a column value. It was a real pain in the butt.

This solution makes use of `with-temp-buffer' to store and parse the
data. In yesterday's question I struggled with understanding how to
use EmacsLisp's regexp faculties. Turns out, they're much easier to
use inside of a buffer. Consequently, much of my code includes
operations inside of a temp buffer.


Sample Data
......................................................................
,----
|     [D]
| [N] [C]
| [Z] [M] [P]
|  1   2   3
`----


Solution
......................................................................
,----
|     (setq stacks-count 9)
| 
|   (defun find-pattern-in-file (filename pattern point-transform)
|     (with-temp-buffer
|       (insert-file-contents filename)
|       (setq case-fold-search nil) ;; might need to toggle via M-x toggle-case-fold-search
|       (goto-char (point-min)) ;; We need to move to start of buffer
|       (let* ((accumulator (list)))
|         (while (re-search-forward pattern nil t)
|           (setq accumulator (append accumulator (list (list (match-string 0)
|                                                       (funcall point-transform (- (match-beginning 0) (line-beginning-position))))))))
|         accumulator)))
| 
|   (defun translate(p)
|     (let ((col nil)
|           (start 0)
|           (end 0))
|       (dotimes (i 9)
|         (setq start (* i 4)) ;; 4 because '[N] ' contains 4 characters, and is considered one column.
|         (setq end (+ (* i 4) 4))
|         (cond ((not (eq nil col))
|                col)
|               ((and (>= p start)
|                     (<= p end))
|                 (setq col i))))
|       col))
| 
|   (progn
|     (let* ((boxes (find-pattern-in-file "./data.txt" "[A-Z]" #'(lambda(p)
|                                                                  (+ (translate p) 1))))
|            (movements-ungrouped (nthcdr 9 (find-pattern-in-file "./data.txt" "[0-9]+" #'(lambda(p) nil))))
|            (movements-grouped (list)))
|       (cl-do* ((i 0 (+ i 3)))
|           ((>= i (length movements-ungrouped)))
|             (let ((count (car (nth i movements-ungrouped)))
|                   (from (car (nth (+ i 1) movements-ungrouped)))
|                   (to (car (nth (+ i 2) movements-ungrouped))))
|               (setq movements-grouped (append movements-grouped (list (list (string-to-number count) (string-to-number from) (string-to-number to)))))))
|       (setq rearranged-boxes (with-temp-buffer
|         (dotimes (i (+ 1 stacks-count))
|           (insert "\n"))
|         (cl-do* ((i (- (length boxes) 1) (- i 1)))
| 
|             j((< i 0))
|           (let* ((box (car (nth i boxes)))
|                  (column (cadr (nth i boxes))))
|             (goto-line column)
|             (insert box)))
|         (cl-do ((i 0 (+ i 1))) ((= i (length movements-grouped)))
|           (let* ((m (nth i movements-grouped))
|                  (count (nth 0 m))
|                  (from (- (nth 1 m) 0))
|                  (to (- (nth 2 m) 0)))
|             (goto-line from)
|             (line-beginning-position)
|             (let ((box (buffer-substring (point) (+ (point) count))))
|               (goto-line to)
|               (insert box)) ;; (insert (reverse box)) ;; for part 1 use reverse
|             (goto-line from)
|             (line-beginning-position)
|             (delete-region (point) (+ (point) count))))
|         (dotimes (i (+ 1 stacks-count))
|           (goto-line i)
|           (delete-region (+ (point) 1) (line-end-position)))
|         (replace-string-in-region "\n" "")
|         (buffer-string)))
|       (mapconcat (lambda(s) s) (split-string rearranged-boxes "\n" t) "")))
`----


Day 6
----------------------------------------------------------------------
I completed today's problem in about 30 minutes. I made use of some
new-to-me sequence functions like `seq-take' and `seq-uniq' to grab
and compare parts of lists.

,----
|   (defun file-contents (filename)
|     (with-temp-buffer
|       (insert-file-contents filename)
|       (buffer-string)))
| 
|   ;; Part 1
|   (let ((message (split-string (file-contents "data.txt") "" t))
|         (start nil))
|     (dotimes (i (- (length message) 4))
|       (let* ((maybe-header (seq-take (nthcdr i message) 4)))
|         (cond ((and (eq nil start)
|                     (eq 4 (seq-length (seq-uniq maybe-header))))
|                (setq start (+ 4 i))))))
|     (print start))
| 
|   ;; Part 2
|   (let ((message (split-string (file-contents "data.txt") "" t))
|         (start nil)
|         (m-start nil))
|     (dotimes (i (- (length message) 4))
|       (let* ((maybe-header (seq-take (nthcdr i message) 4)))
|         (cond ((and (eq nil start)
|                     (eq 4 (seq-length (seq-uniq maybe-header))))
|                (setq start (+ 4 i)))
|               ((and (numberp start) (eq nil m-start))
|                (let ((maybe-message (seq-take (nthcdr i message) 14)))
|                  (cond ((eq 14 (seq-length (seq-uniq maybe-message)))
|                         (setq m-start (+ 14 i))
|                         ))
|                  )))))
|     (print m-start))
`----