Advent Of Code 2025: Clojure Edition!
It’s finally December, and this year my college decided to end exams early, so i can for the first time truly enjoy Advent of Code! I have decided to attempt it in clojure, as it is one of the lisps i have never used, and it is widely used in the industry compared to other lisps. I’d like to discuss the algorithms (which are horribly bad), and more importantly the code i came up with to implement the algorithm.
All of the code can be viewed and ran at https:www.gitlab.com/binarydigitz01/aoc2025
Day 1
Part 1
(defn read-input [& [file]]
(line-seq (io/reader (or file "data/dec1.txt"))))
(defn get-rots [input]
(defn get-rot [line]
(let* [lttr (get line 0)
sign (if (= lttr \L) -1 1)]
(* sign (parse-long (subs line 1)))))
(map get-rot input))
(defn solve []
(let* [input (read-input)
rots (get-rots input)]
(loop [lst rots
zero-count 0
curr 50]
(if (empty? lst)
zero-count
(let [next (mod (+ curr (first lst)) 100)]
(recur (rest lst)
(if (= next 0) (+ 1 zero-count) zero-count)
next))))))
Algorithmically I iterate over the rotations, and everytime the knob points to 0, i increment the zero-count to 1. Nothing too complex. I would however like to discuss the loop-recur syntax that is specific to clojure.
As someone who has learnt a little bit of common lisp and scheme, i was surprised to hear that clojure does not have direct tail-recursion, because the JVM itself does not have tail-recursion (sigh, Java please get your stuff together). loop-recur is one of the ways in clojure with which you can utilise tail recursion.
While I was looking at ways to iterate over a list, I realized that clojure does not have car and cdr. It does have first and rest which perform the same function (yes i am aware this exists also in scheme, i do prefer car and cdr though). However looking deeper, I realized that lists in clojure are not made up of cons cells. This is the biggest difference I have found in clojure which separates it from scheme and common lisp. Now it is for the better or for worse I have no idea. I’m sure it has to do with performance reasons, as clojure is compiling the program for JVM.
Another thing i googled and learnt from stackoverflow was the special syntax for “default arguments”, which is essentially using ‘or’ to provide a default value if the the caller has not provided the optional argument in read-input.
Part 2
Here is my solution:
(defn count-zeros-cw [start turns]
(let* [init (- 100 (mod start 100))
rem-turns (- turns init)]
(if (>= rem-turns 0)
(+ 1 (long (/ rem-turns 100)))
0)))
;; assuming turns should be positive
(defn count-zeros-acw [start turns]
(let* [init (mod start 100)
rem-turns (- turns init)]
(if (>= rem-turns 0)
(+ (if (= start 0) 0 1) (long (/ rem-turns 100)))
0)))
(defn count-zeros [start turns]
(if (> turns 0)
(count-zeros-cw start turns)
(count-zeros-acw start (- turns))))
(defn solve []
(let* [input (dec1/read-input)
rots (dec1/get-rots input)]
(loop [lst rots
zero-counter 0
curr 50]
(if (empty? lst)
zero-counter
(recur (rest lst)
(+ zero-counter (count-zeros curr (first lst)))
(mod (+ curr (first lst)) 100))))))
I use modulus and some integer division magic to figure out everytime the knob would cross or end at 0.
Day 2
Part 1
(defn read-input [& [file]]
(first (line-seq (io/reader (or file "data/dec2.txt")))))
(defn process-input [input]
(let* [step1 (str/split input #",")
step2 (map
(fn [range]
(let* [str-vec (str/split range #"-")
res (list (parse-long (first str-vec)) (parse-long (second str-vec)))]
res))
step1)]
step2))
(defn get-invalid-ids [pair]
(loop [lst (range (first pair) (inc (second pair)))
sum 0]
(if (empty? lst)
sum
(let* [elm (first lst)
len (+ 1 (long (math/log10 elm)))
base (long (math/pow 10 (/ len 2)))
fhalf (long (/ elm base))
shalf (long (mod elm base))]
(if (or (odd? len) (not= fhalf shalf))
(recur (rest lst)
sum)
(recur (rest lst)
(+ sum
elm)))))))
(defn solve []
(let [ranges (process-input (read-input))]
(apply + (map get-invalid-ids ranges))))
Incredibly simple, I iterate over every number in the given ranges, and then use some basic math to get the first half and second half of the number. If they’re equal i add it to the sum.
One thing I want to talk about is the “chain” of variables i made in let* in ‘get-invalid-ids’. Usually in scheme, I use set! multiple times on the same variable to perform intermediate steps, however I am unfamiliar with the functional way to do it in. The best I could come up with for now is this chain, where I create a new variable for every step i perform. Perhaps i’m not using enough functions, as this approach is something I would use in imperative programming. Doesn’t seem very pretty to me.
Part 2
(defn check-split? [num split]
(let* [l (count num)
get-char-split (fn [index] (get num (mod index split)))]
(and
(not= l 1)
(= (mod l split) 0)
(loop [i (range l)]
(cond
(empty? i) true
(not=
(get num (first i))
(get-char-split (first i))) false
:else (recur (rest i)))))))
(defn invalid-id? [num] ;; string
(let [lim (inc (math/ceil (/ (count num) 2)))]
(loop [i (range 1 lim)]
(cond
(empty? i) false
(check-split? num (first i)) true
:else (recur (rest i))))))
(invalid-id? "")
(defn get-invalid-ids-part2 [pair]
(loop [lst (range (first pair) (inc (second pair)))
sum 0]
(if (empty? lst)
sum
(if (invalid-id? (str (first lst)))
(recur (rest lst)
(do (+ sum (first lst))))
(recur (rest lst)
sum)))))
(defn solve-part2 []
(let [ranges (process-input (read-input))]
(apply + (map get-invalid-ids-part2 ranges))))
Sadly for this part, I brute forced my way through. The problem essentially is reduced to: In a given number, find if it is made up of a repeatable sequence, for e.g 121212 is made of 12. I iterate from a split of 1, till half of the lenght of the number to see if any repeating sequences are found. This method is incredibly slow, and i believe a faster way is to generate numbers with repeating sequences instead. However i came up with the second method way too late, so here we are.
Syntax wise for this I’m satisfied.
I’ll try to continue writing these blog posts everyday for new parts, as i learn more and more about clojure!