Skip to main content Arjen Wiersma

Advent of Code 2025 Day 4

Day 4 of solving The Advent of Code in 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.

clojure code snippet start

(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)

clojure code snippet end

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