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. IMG iillustration-11.png 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 `---- IMG iadvent-of-code-2.png 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)) `----