Skip to content

Latest commit

 

History

History
127 lines (103 loc) · 7.48 KB

day11.md

File metadata and controls

127 lines (103 loc) · 7.48 KB

Day 11: Cosmic Expansion

Intro

Today's puzzle was much easier than yesterday's, working with some basic grids. The wording for part 2 is, in my opinion, incredibly misleading. I solved the puzzle without understanding why I had to create an off-by-one error, and only while doing this write-up did I figure out why this ended up being correct. Very poor wording in the instructions.

Part One

We are given a simple grid with galaxies marked as hash signs. We need to expand all rows or columns without galaxies by one extra space, and then calculate the sum of shortest paths between the resulting galaxy locations. Usually I create a parse function here, but I found I didn't want one. Instead, I'll operate entirely on sequences of [x y] coordinates.

The bulk of this solution depends on the expand-universe function, which in turn depends on expand-row and expand-column. Let's start with those helper functions.

(defn expand-row [galaxies row]
  (map (fn [[x y :as coords]] (if (> y row) [x (inc y)] coords)) galaxies))

(defn expand-column [galaxies column]
  (map (fn [[x y :as coords]] (if (> x column) [(inc x) y] coords)) galaxies))

The two functions do the same things in different dimensions, so let's just look at expand-row. This function takes in a sequence of galaxies, and the row number to expand; a row corresponds to a coordinate's y ordinate. So we'll map each set of coordinates within the input galaxies, checking to see if that coordinate's y value is creater than the row being expanded. If so, increment its value; if not, return the input coordinates unchanged.

(defn expand-universe [galaxies bounds]
  (let [[[x0 y0] [x1 y1]] bounds
        expansion-rows (remove (set (map second galaxies)) (range y1 (dec y0) -1))
        expansion-columns (remove (set (map first galaxies)) (range x1 (dec x0) -1))]
    (as-> galaxies x
          (reduce #(expand-row %1 %2) x expansion-rows)
          (reduce #(expand-column %1 %2) x expansion-columns))))

expand-universe takes in the sequence of galaxies, as well as the bounding box bounds, which we'll immediately deconstruct into [[x0 y0] [x1 y1]]; note that we could do this in the function arguments, so that line would look like (defn expand-universe [galaxies [[x0 y0] [x1 y1]]] ...), but I think that's uglier than an internal let binding. Then we need to figure out which rows and columns need to be expanded, and again we'll just look at the rows. To start, we'll create a range of all possible y values, starting from the largest value of y1 and decrementing down to (dec y0); doing this will return a sequence of rows from largest to smallest; we could calculate how many total rows each point needs to move down, and perhaps that would be more efficient, but starting from largest to smallest means we can do each expansion safely without having a smaller row number impacting which galaxies get expanded later. Once we have these two list of decreasing row and column numbers, we use the as-> macro, which allows us to pipe the output of each function call into the next one in any position. We'll call reduce on expand-row and expand-column for each row and column, passing the collection of galaxies neatly from one reduce to the next.

Finally, we can implement part1.

(defn part1 [input]
  (let [points (p/parse-to-char-coords input)
        bounds (p/bounding-box (map first points))
        galaxies (keep (fn [[p c]] (when (= c \#) p)) points)
        expanded-galaxies (expand-universe galaxies bounds)]
    (apply + (for [g1 expanded-galaxies
                   g2 expanded-galaxies
                   :when (= 1 (compare g1 g2))]
               (p/manhattan-distance g1 g2)))))

We'll parse the entire input grid into the sequence points so we can find the proper bounding box as bounds. Then we can strip out the galaxy coordinates by calling keep on the points, searching for ones where the value in the grid is the character #, and when so pulling out just the coordinates. We then bind expanded-galaxies to the output of expand-universe, thus giving us the fully expanded galaxies. Now we need to create every unique pair of galaxies, and I thought this would be a good time to use list comprehension with the for macro. For each galaxy g1 in expanded-galaxies, we look at each value g2 in expanded-galaxies, stopping the search in g2 once (= g1 g2). We could also only look at a given pair of g1 and g2 by comparing the two and keeping only ones where g1 compares less than g2, but using this :when instead of a :while is a simpler way to only look at half of the data. Come to think of it, we could have used all values and cut the results in half, but that's stilly. Anyway, for each pair, the shortest distance on a 2-dimensional grid is the manhattan-distance, which we'll reuse from previous years' solutions; this is in the abyala.advent-utils-clojure.point namespace. Finally, we add together these distances to solve the puzzle.

Day Two

Part 2 isn't difficult to do, other than dealing with some confusing instructions. The problem says "Now, instead of "the expansion you did before, make each empty row or column one million times larger." I read that multiple times as meaning that we add 1,000,000 rows or columns for each expansion row or column, but that's not exactly what it says. To make a row 1,000,000 times larger, we need to add 999,999 rows, making the total row one million lines tall; we are not making the increased row size one million times larger, or even one million times the previous size. So when the instructions mention making the expansion 10 times larger, we need to add 9 rows/columns. Yikes.

Anyway, after figuring out the wording, this puzzle requires refactoring all the existing functions to take in a function argument expand-by, such that part1 passes in a value of 1 and part2 passes in 999999.

(defn expand-row [expand-by galaxies row]
  (map (fn [[x y :as coords]] (if (> y row) [x (+ y expand-by)] coords)) galaxies))

(defn expand-column [expand-by galaxies column]
  (map (fn [[x y :as coords]] (if (> x column) [(+ x expand-by) y] coords)) galaxies))

(defn expand-universe [expand-by galaxies [[x0 y0] [x1 y1]]]
  (let [expansion-rows (remove (set (map second galaxies)) (range y1 (dec y0) -1))
        expansion-columns (remove (set (map first galaxies)) (range x1 (dec x0) -1))]
    (as-> galaxies x
          (reduce #(expand-row expand-by %1 %2) x expansion-rows)
          (reduce #(expand-column expand-by %1 %2) x expansion-columns))))

(defn solve [expand-by input]
  (let [points (p/parse-to-char-coords input)
        bounds (p/bounding-box (map first points))
        galaxies (keep (fn [[p c]] (when (= c \#) p)) points)
        expanded-galaxies (expand-universe expand-by galaxies bounds)]
    (apply + (for [g1 expanded-galaxies
                   g2 expanded-galaxies
                   :while (not= g1 g2)]
               (p/manhattan-distance g1 g2)))))

(defn part1 [input] (solve 1 input))
(defn part2 [input] (solve 999999 input))                   ; Not 1,000,000

The only functions that don't just pass expand-by around are expand-row and expand-column. Instead of their invoking (inc y), then now call (+ y expand-by), and the equivalent calls for x.

So yeah, super easy code, but damn confusing instructions.