Skip to content

Commit 1e0b6ae

Browse files
committed
day 11
1 parent 6260153 commit 1e0b6ae

6 files changed

Lines changed: 869 additions & 2 deletions

File tree

clojure/deps.edn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{:paths ["src" "tests" "dev"]
22
:deps {org.clojure/clojure {:mvn/version "1.12.3"}
3-
com.github.narimiran/aoc-utils {:git/tag "v0.9.1" :git/sha "122f474"}
3+
com.github.narimiran/aoc-utils {:git/tag "v0.9.3" :git/sha "e951804"}
44
criterium/criterium {:mvn/version "0.4.6"}
55
io.github.nextjournal/clerk {:mvn/version "0.18.1158"}
66
quil/quil {:mvn/version "4.3.1563"}}

clojure/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ Day 7: [Laboratories](https://adventofcode.com/2025/day/7) | [d
2828
Day 8: [Playground](https://adventofcode.com/2025/day/8) | [day08.clj](src/day08) | hash-set, disj | | No Manhattan distance? Wow!
2929
Day 9: [Movie Theater](https://adventofcode.com/2025/day/9) | [day09.clj](src/day09) | every?, pmap, ffirst | | The hardest one so far.
3030
Day 10: [Factory](https://adventofcode.com/2025/day/10) | [day10.clj](src/day10) | constantly, not | | Just Part 1 this time.
31+
Day 11: [Reactor](https://adventofcode.com/2025/day/11) | [day11.clj](src/day11) | | | Surprisingly easy.

clojure/src/day11.clj

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
^{:nextjournal.clerk/visibility {:code :hide :result :hide}}
2+
(ns day11
3+
{:title "Reactor"
4+
:url "https://adventofcode.com/2025/day/11"
5+
:extras ""
6+
:highlights ""
7+
:remark "Surprisingly easy."
8+
:nextjournal.clerk/auto-expand-results? true
9+
:nextjournal.clerk/toc true}
10+
(:require [aoc-utils.core :as aoc]))
11+
12+
13+
14+
15+
;; # Day 11: [Reactor](https://adventofcode.com/2025/day/11)
16+
;;
17+
;; The Elves have installed a new server rack and now need our help with
18+
;; connecting some devices with cables.
19+
;; They give us a list of connections between the devices which looks like
20+
;; this:
21+
22+
(def example "aaa: you hhh
23+
you: bbb ccc
24+
bbb: ddd eee
25+
ccc: ddd eee fff
26+
ddd: ggg
27+
eee: out
28+
fff: out
29+
ggg: out
30+
hhh: ccc fff iii
31+
iii: out")
32+
33+
;; Each line tells us how to connect a device's outputs to the correct devices.
34+
;; For example, `aaa: you hhh` means we need to connect device `aaa` to
35+
;; `you` and `hhh`. And the connection is directed, there is no `you` to
36+
;; `aaa` or `hhh` to `aaa` connection.
37+
38+
39+
40+
41+
42+
;; ## Input parsing
43+
;;
44+
;; We're dealing with an acyclic graph, so let's build it immediately in the
45+
;; parsing phase.
46+
47+
48+
(defn build-graph [lines]
49+
(reduce (fn [acc [start & ends]]
50+
(assoc acc start ends))
51+
{}
52+
lines))
53+
54+
(defn parse-data [input]
55+
(-> input
56+
(aoc/parse-lines :keywords #"\W+") ; [1]
57+
build-graph)) ; [2]
58+
59+
;; For each line in the input, we will split the string on `\W+`, which is
60+
;; a regex speak for "one or more non-characters".
61+
;; We will convert each name of a device into a keyword for convenience [1].
62+
;; (I've added `:keywords` to the
63+
;; [aoc-utils library](https://narimiran.github.io/aoc-utils/aoc-utils.core.html)
64+
;; after I've seen this task.)\
65+
;; Since the graph is acyclic, creating a hashmap representation of it is
66+
;; straightforward [2].
67+
68+
(def example-data (parse-data example))
69+
(def data (parse-data (aoc/read-input 11)))
70+
71+
72+
73+
74+
;; ## Part 1
75+
;;
76+
;; For Part 1 we need to count all paths from `:you` to `:out`.
77+
;;
78+
;; For acyclic graphs, when traversing them we don't need to remember what
79+
;; places we've visited, we just go go go until we reach the end.
80+
;;
81+
;; We'll do it recursively. Every time we come to `:out`, we count that as
82+
;; one successful path. If we come to the end of the graph (no neighbours of a
83+
;; current point), the path was not successful. If there are some neighbours
84+
;; of a current point, we try to reach `:out` with all of them.
85+
86+
(def paths
87+
(memoize ; [1]
88+
(fn [graph curr end]
89+
(if (= curr end) 1 ; [2]
90+
(if-let [nbs (graph curr)]
91+
(aoc/sum-map #(paths graph % end) nbs) ; [3]
92+
0))))) ; [4]
93+
94+
;; If there is a path `A - B - C - D - out` and `H - C - D - out` it means we will
95+
;; visit points `C` and `D` multiple times. And we already know from the first
96+
;; visit it will be a successful path. We will use our friend
97+
;; [`memoize`](https://clojuredocs.org/clojure.core/memoize) that we met
98+
;; in the [Day 7 solution](../day07). [1]\
99+
;; For Part 1 we could ignore this inefficiency of these multiple visits,
100+
;; but for Part 2 it is crucial to memoize.
101+
;;
102+
;; If our `curr`ent point is the `end` we wanted, that counts as one successful
103+
;; path [2]. Otherwise, if there are neighbours of the current point, for
104+
;; each of them we recursively call this function and sum up all successful
105+
;; paths [3]. If we reached the end (no neighbours), it was an unsuccessful
106+
;; path and we return zero [4].
107+
;;
108+
;; For Part 1 we need to count the paths from `:you` to `:out`:
109+
110+
(defn part-1 [graph]
111+
(paths graph :you :out))
112+
113+
(part-1 example-data)
114+
(part-1 data)
115+
116+
117+
;; Easy-peasy!
118+
119+
120+
121+
122+
123+
124+
;; ## Part 2
125+
;;
126+
;; There is different example for Part 2:
127+
128+
(def example-2 "svr: aaa bbb
129+
aaa: fft
130+
fft: ccc
131+
bbb: tty
132+
tty: ccc
133+
ccc: ddd eee
134+
ddd: hub
135+
hub: fff
136+
eee: dac
137+
dac: fff
138+
fff: ggg hhh
139+
ggg: out
140+
hhh: out")
141+
142+
(def example-data-2 (parse-data example-2))
143+
144+
;; Now, our task is to find the number of paths starting from `:svr`,
145+
;; passing through both `:dac` and `:fft`, and ending with `:out`.
146+
;;
147+
;; As expected, the search space is huge. Memoizing will be important, but it
148+
;; is not enough. In a true recursive fashion, let's break the problem down
149+
;; into simpler problems.
150+
;;
151+
;; If our task is to count the ways we can go from point `A` to point `C` via
152+
;; point `B`, we can split it in counting the ways from `A` to `B` and then
153+
;; the ways from `B` to `C`. The final result is achieved by multiplying
154+
;; these two intermediate results.
155+
;;
156+
;; There is another thing to notice. Since our graph is acyclic, this means
157+
;; there is _either_ a connection from `:dac` to `:fft` or a connection
158+
;; from `:fft` to `:dac`, it can't be both.\
159+
;; So we will first check which of these two is a valid connection, and
160+
;; depending on that we will calculate the remaining paths.
161+
162+
163+
(defn part-2 [graph]
164+
(let [fft-dac (paths graph :fft :dac)] ; [1]
165+
(if (zero? fft-dac) ; [2]
166+
(* (paths graph :svr :dac)
167+
(paths graph :dac :fft)
168+
(paths graph :fft :out))
169+
(* (paths graph :svr :fft)
170+
fft-dac
171+
(paths graph :dac :out)))))
172+
173+
;; As stated above, we first calculate the number of paths from `:fft`
174+
;; to `:dac` [1].
175+
;; If there are zero paths [2], then we know we need to do
176+
;; `svr -> dac -> fft -> out`.
177+
;; Otherwise, we correctly guessed the correct direction and we calulate
178+
;; the remaining steps of the `svr -> fft -> dac -> out`.
179+
180+
(part-2 example-data-2)
181+
(part-2 data)
182+
183+
184+
185+
186+
187+
;; ## Conclusion
188+
;;
189+
;; This one was a surprisingly easy and straightforward task, escpecially for
190+
;; the next to last task this year. But I guess we deserved some breathing
191+
;; room after yesterday's task.
192+
;;
193+
;; No new functions to highlight today.
194+
195+
196+
;; ----
197+
;;
198+
;; [< Previous solution](../day10)
199+
;; | [Source code](https://github.com/narimiran/aoc2025/blob/main/clojure/src/day11.clj)
200+
;; | [Next solution >](../day12)
201+
202+
203+
204+
^{:nextjournal.clerk/visibility {:code :hide :result :hide}}
205+
(defn -main [input]
206+
(let [data (parse-data input)]
207+
[(part-1 data)
208+
(part-2 data)]))

clojure/tests/solutions_test.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[aoc-utils.core :as aoc]
44
day01 day02 day03 day04
55
day05 day06 day07 day08
6-
day09 day10 ;day11 ;day12
6+
day09 day10 day11 ;day12
77
[clojure.test :refer [deftest is]]))
88

99

@@ -30,3 +30,4 @@
3030
(check-day 8 nil [352584 9617397716])
3131
(check-day 9 [50 24] [4781235324 1566935900])
3232
(check-day 10 7 538)
33+
(check-day 11 [5 0] [555 502447498690860])

0 commit comments

Comments
 (0)