Advent of Code 2025 Day 4

Day 4 of solving {{< backlink “aoc” “The Advent of Code” >}} in {{< backlink “clojure” “Clojure” >}}.

The first grid problem of the season! The first part was really suspiciously easy, read in the grid, find all the rolls and look at its 8 neighbors, eliminating it if there are less than 4. The 2nd part was a nice progression on this.

Instead of just saying how many should be eliminated, we do it until there are no eliminations possible. This is highly reminiscent of a Conway Game of Life puzzle. In Clojure this is quite nicely done by reading the grid as a vector, and then ranging over all the coordinates. On each coordinate just take a look at the neighbors and apply the logic. Its pure nature means that the functions are already working in the right way to do this repeatedly.

(ns day4
  (:require
   [clojure.string :as str]))

(defn get-cell [grid [row col]]
  (when (and (>= row 0) (< row (count grid))
             (>= col 0) (< col (count (first grid))))
    (get-in grid [row col])))

(defn neighbors-8 [grid [row col] & {:keys [filter-fn] :or {filter-fn (constantly true)}}]
  (->> (for [r (range (dec row) (+ row 2))
             c (range (dec col) (+ col 2))
             :when (not= [r c] [row col])]
         [r c])
       (map #(get-cell grid %))
       (remove nil?)
       (filter filter-fn)))

(defn all-coords [grid]
  (for [row (range (count grid))
        col (range (count (first grid)))]
    [row col]))

(defn print-grid-with-marks [grid marked-positions]
  (doseq [row (range (count grid))]
    (doseq [col (range (count (first grid)))]
      (let [pos [row col]
            cell (get-in grid pos)]
        (print (if (some #(= % pos) marked-positions)
                 "X "
                 (str cell " ")))))
    (println)))

(defn mark-movable [grid]
  (let [all-pos (all-coords grid)
        marks (remove nil? (map (fn [pos]
                                  (let [n (neighbors-8 grid pos :filter-fn #(not= % \.))]
                                    (when (and (= \@ (get-cell grid pos)) (< (count n) 4))
                                      pos)))
                                all-pos))]
    marks))

(defn remove-marked [grid marked-positions]
  (let [rows (count grid)
        cols (count (first grid))]
    (vec
     (for [row (range rows)]
       (vec
        (for [col (range cols)]
          (let [pos [row col]
                cell (get-in grid pos)]
            (if (and (= cell \@) (some #(= % pos) marked-positions))
              \.
              cell))))))))

(def play
  (fn [grid]
    (loop [g grid
           moved 0]
      (let [marks (mark-movable g)
            mark-count (count marks)]
        (if (= 0 mark-count)
          moved
          (recur (remove-marked g marks)
                 (+ moved mark-count)))))))

(def grid
  (->> "resources/4.in"
       slurp
       str/split-lines
       (mapv vec)))

;; part 1
(count (mark-movable grid))

;; part 2
(play grid)

Really enjoyed it and well within my daily commute to solve.

#programming