Advent of Code Day 3
This one took me too long and I should probably implement it differently. It had a part 2 which I didn’t do but gave me a clue as to a maybe better way to do it. I might reimplement later but I need to press on.
(defn symbol? [c]
(if (#{\* \# \+ \$ \@ \% \- \& \/ \=} c) :symbol nil))
(defn wazzit? [c]
(or (digit? c)
(symbol? c)
:dot))
(defn string-of [n s]
(apply str (repeat n s)))
(defn symbol-adjacent? [prev-row next-row col-window idx]
(let [start (if (zero? idx) 0 (dec idx))
end (if (= (inc idx) (count prev-row)) (inc idx) (inc (inc idx)))]
(or (some #{:symbol} col-window)
(some #{:symbol} (subvec prev-row start end))
(some #{:symbol} (subvec next-row start end)))))
(defn aoc3 []
(let [results (atom {:total 0 :current-num [0] :num-finished? true :symbol-adjacent? false})
schematic (line-seq (io/reader "./aoc3input.txt"))
row-windows (vec (map vec (partition 3 1
(concat
[(string-of (count (first schematic)) ".")]
schematic
[(string-of (count (first schematic)) ".")]))))]
(doseq [[prev-row row next-row] row-windows]
(let [prev-row (vec (map wazzit? prev-row))
col-windows (map vec (partition 3 1 (concat [:dot] (map wazzit? row) [:dot])))
next-row (vec (map wazzit? next-row))]
(doseq [idx (range (count col-windows))]
(let [ch (second (nth col-windows idx))]
(cond
(digit? ch) (swap!
results assoc
:current-num (conj (:current-num @results) (digit? ch))
:num-finished? false
:symbol-adjacent? (if (:symbol-adjacent? @results)
(:symbol-adjacent? @results)
(symbol-adjacent?
prev-row
next-row
(nth col-windows idx)
idx)))
:else (swap!
results assoc
:total (if (:symbol-adjacent? @results)
(+ (:total @results)
(Integer/parseInt (apply str (:current-num @results))))
(:total @results))
:num-finished? true
:symbol-adjacent? false
:current-num [0]))))))
@results))
- The
wazzit
function uses thedigit
function from my day 1 solution. It just converts all the characters into either:symbol
,:dot
, or a digit. Not strictly necessary but it made things cleaner in my mind. - The general approach was to use the
partition
function to assemble “windows” of rows and columns. Each row of the input schematic then has a previous row and a next row. Also each character within a row has a previous and next character. To do this, you need to add rows to the top and bottom of the entire array and columns before and after. I convert everything to vectors since we need indexed lookup to these arrays. - We then process each row in a
doseq
, construct the column windows for the row and process each column window through acond
statement. - We have an atom that holds an accumulated total and a few state variables. If the
cond
encounters a digit, it checks for adjacency to a symbol using thesymbol-adjacent?
function. If it finds a symbol it starts to collect digits in the:current-num
key of the atom. When a non-digit is encountered, the number is created, added to the total, and the state flags are reset.
The alternate approach would be to progress through the arrays looking for symbols instead of digits. When you encounter a symbol, you call some function that collects all the adjacent positions and filters out the digits. Then, for each digit, you have a second function that checks left and right for more digits until it gets the entire number. You’d have to have a state variable that holds a set of the positions you have checked that have digits so that you don’t grab a number more than once. This implementation would make it fairly easy to complete part 2 of the challenge.