Skip to content

Commit

Permalink
day 14
Browse files Browse the repository at this point in the history
  • Loading branch information
narimiran committed Dec 14, 2024
1 parent a814f5f commit 71313cc
Show file tree
Hide file tree
Showing 6 changed files with 827 additions and 1 deletion.
13 changes: 13 additions & 0 deletions clojure/aoc.clj
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@
([v pred mult] (grid->point-set v pred mult)))


;; Sometimes we need to inspect a grid.
;; With `points->lines`, we create a list of points in each line.
;;
(defn points->lines [points]
(if (map? points) (points->lines (set (keys points)))
(let [x-lim (inc (reduce max (map first points)))
y-lim (inc (reduce max (map second points)))]
(for [y (range y-lim)]
(str/join (for [x (range x-lim)]
(if (points [x y])
\█ \space)))))))



;; ### 2D grids
Expand Down Expand Up @@ -451,6 +463,7 @@
long
inc)))

;; (defn )



Expand Down
306 changes: 306 additions & 0 deletions clojure/day14.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
^{:nextjournal.clerk/visibility {:code :hide :result :hide}}
(ns day14
{:nextjournal.clerk/auto-expand-results? true
:nextjournal.clerk/toc :collapsed}
(:require
aoc
[quil.core :as q]
[quil.middleware :as m]
[nextjournal.clerk :as clerk]))



;; # Day 14: Restroom Redoubt
;;
;; We're at Easter Bunny Headquarters and one of The Historians needs
;; to use the bathroom.
;; But there are robots guarding the area outside the bathroom.
;; They are moving in predictable straight lines and our task is to find
;; out where will they be after 100 seconds.
;;
;; For each robot we have its current position (at time 0) and its
;; velocity, e.g. `p=2,4 v=2,-3`.
;;
;; NOTE: Since the task changes some parameters between the example and
;; the real input, we won't be solving it for the example.





;; ## Input parsing
;;
;; To get a position and a velocity of a robot, we need to extract all
;; integers from an input line.
;;
;; Since the positions will change every second, while the velocities stay
;; constant, we will split them in two separate lists:
;;
(defn parse-data [input]
(let [robots (for [[px py vx vy] (aoc/parse-lines input :ints)]
[[px py] [vx vy]])
positions (mapv first robots)
velocities (mapv second robots)]
[positions velocities]))

(def data (parse-data (aoc/read-input 14)))


;; The task defines the `width` and `height` of the floor as:
;;
(def width 101)
(def height 103)




;; ## Part 1
;;
;; Our task for Part 1 consists of two tasks:
;; - find the positions of robots after 100 seconds
;; - count the number of robots in each quadrant of the floor
;;
;; Moving a robot is easy.
;; We move it `n` times, making sure it is still inside of the floor
;; using `mod` (it works also for negative number):
;;
(defn move-robot
([pos vel] (move-robot 1 pos vel))
([n [px py] [vx vy]]
(let [px' (mod (+ px (* n vx)) width)
py' (mod (+ py (* n vy)) height)]
[px' py'])))

;; The function has two arities.
;; We can call it with two arguments and get the next position (after 1 second)
;; of a robot, or if we call it with three arguments, we get the position
;; of a robot after `n` seconds.

(let [p [2 4]
v [2 -3]]
[(move-robot p v)
(move-robot 100 p v)])






;; For the second task, we can divide the floor in four quadrants like this:
;; ```
;; ..... .....
;; .-.-. .+.-.
;; ..... .....
;; C
;; ..... .....
;; .-.+. .+.+.
;; ..... .....
;; ```
;;
;; `C` is a point in the centre of the floor.
;; Points in the top-left quadrant have both x- and y- positions smaller than `C`.
;; Points in the top-right quadrant have x-position larger than x-position of `C`
;; and lower y-position. Etc.
;;
;; Our task is to count the number of robots in each quadrant.
;; To do that, we will check how many robots satisfy the conditions of
;; a quadrant:
;;
(defn count-in-quadrant [positions [cx cy] [x-cond y-cond]]
(aoc/do-count [[x y] positions
:when (and (x-cond x cx)
(y-cond y cy))]))


;; Putting it all together, we get this:
;;
(defn part-1 [[positions velocities]]
(let [positions' (mapv (partial move-robot 100)
positions
velocities)
center [(quot width 2) (quot height 2)]
quad-count (partial count-in-quadrant positions' center)
quad-conds [[< <] ; top-left
[> <] ; top-right
[< >] ; bottom-left
[> >]]] ; bottom-right
(aoc/prod-map quad-count quad-conds)))


;; Several things to highlight:
;; - For each robot, we will move it 100 times, so we can partially apply
;; the `move-robot` function by using
;; [`partial`](https://clojuredocs.org/clojure.core/partial).
;; - We are taking the advantage of `map/mapv` taking multiple arguments which are
;; passed in parallel to the function we're calling.
;; Here, the function will receive two arguments: the first element of
;; `positions` and the first element of `velocities`, then the second element
;; of each, etc.
;; - One more usage of `partial`. The only thing that changes when calling
;; the `count-in-quadrant` function are the conditions of each quadrant,
;; so the `positions'` and `center` can be partially applied in advance.
;; - We are interested in a product of numbers in each quadrant, so I'm
;; using the `aoc/prod-map` function, which is an equivalent of
;; `(reduce * (map ...))`.
;;

(part-1 data)







;; ## Part 2
;;
;; We need to find an _Easter_ egg: a _Christmas_ tree.
;;
;; I've seen people use different approaches for this task.\
;; The one I used is kind of hinted in Part 1, where we're shown some
;; situations where two robots occupy the same space.
;; I've guessed that for an image to appear, every robot should be in its
;; own position, with no overlapping.
;;
;; I'm using the [`distinct?` function](https://clojuredocs.org/clojure.core/distinct_q)
;; to check if all positions are unique.
;; If they are, we've got the solution.
;; If not, we move robots for one second and check again.
;;
(defn part-2 [[positions velocities]]
(reduce
(fn [positions n]
(if (apply distinct? positions)
(reduced n)
(mapv move-robot positions velocities)))
positions
(range)))


(part-2 data)








;; ## Visualization
;;
;; Let's see how that Christmas tree looks like:
;;
(let [[positions velocities] data
positions' (mapv (partial move-robot 8168) positions velocities)
axes-common {:showgrid false
:zeroline false}]
(clerk/plotly
{:config {:displayModeBar false
:displayLogo false}
:data [{:x (mapv first positions')
:y (mapv second positions')
:mode :markers
:marker {:symbol :square
:size 5}}]
:layout {:xaxis axes-common
:yaxis (merge axes-common {:autorange :reversed})
:height 700
:width 700
:margin {:l 0 :r 0 :t 0 :b 0}
:showlegend false}}))






;; ## Animation
;;
;; Ok, we have a final image, but let's also see how we got there!
;;
;; Here are the last few seconds of robot movements in a slow-motion:
;;
{:nextjournal.clerk/visibility {:result :hide}}

(defn setup []
(let [[pos vel] data
step 1/180
start (- 8168 (* 600 step))
init-pos (mapv (partial move-robot start) pos vel)]
(q/frame-rate 60)
(q/no-stroke)
{:pos init-pos
:step-size step
:vel vel
:step start}))

(defn update-state [{:keys [pos vel step step-size] :as state}]
(-> state
(assoc :pos (mapv (partial move-robot step-size) pos vel))
(update :step + step-size)))

(defn draw-state [{:keys [pos step]}]
(q/scale 7)
(q/background 15 15 35)
(when (= step 8168)
(q/exit))
(doseq [[x y] pos]
(q/fill 255 255 102)
(q/rect x y 1 1))
#_(q/save-frame "/tmp/imgs/day14-###.jpg"))

(comment
(q/sketch
:size [(* 7 (inc width))
(* 7 (inc height))]
:setup #'setup
:update #'update-state
:draw #'draw-state
:middleware [m/fun-mode]))


;; To make a video from the frames created above, I'm using the following command:\
;; `ffmpeg -framerate 60 -i /tmp/imgs/day14-%03d.jpg -vf tpad=stop_mode=clone:stop_duration=2 -c:v libx264 -pix_fmt yuv420p imgs/day14.mp4`
;;
;; And the result is:
;;
^{:nextjournal.clerk/visibility {:code :hide
:result :show}}
(clerk/html
[:video {:controls true}
[:source {:src "https://i.imgur.com/AnVFvb8.mp4"
:type "video/mp4"}]
"Your browser does not support the video tag."])







;; ## Conclusion
;;
;; The hardest part of today's task was to figure out when/how the image
;; of a Christmas tree would appear.\
;; The coding part of the task was easy.
;;
;; Today's highlights:
;; - multi-arity functions
;; - `partial`: partially apply a function
;; - `distinct?`: check uniqueness of its arguments











^{:nextjournal.clerk/visibility {:code :hide :result :hide}}
(defn -main [input]
(let [data (parse-data input)]
[(part-1 data)
(part-2 data)]))
5 changes: 5 additions & 0 deletions clojure/tests/aoc_tests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@


(def grid ["#.." "..#" "##."])
(def grid-print ["" "" "██ "])
(def walls {[0 0] \# , [2 1] \# , [0 2] \# , [1 2] \#})

(deftest vec->map
Expand All @@ -98,6 +99,10 @@
(deftest vec->set
(is (= (set (keys walls)) (aoc/grid->point-set grid #{\#}))))

(deftest points->lines
(is (= grid-print (aoc/points->lines walls)))
(is (= grid-print (aoc/points->lines (set (keys walls))))))

(deftest graph-traversal
(let [walls #{[0 1] [1 1] [1 3] [2 3] [3 0] [3 1] [3 2] [3 3]}
with-hole (disj walls [3 1])
Expand Down
3 changes: 2 additions & 1 deletion clojure/tests/solutions_tests.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
(:require
day01 day02 day03 day04 day05
day06 day07 day08 day09 day10
day11 day12 day13 ;day14 day15
day11 day12 day13 day14 ;day15
;; day16 day17 day18 day19 day20
;; day21 day22 day23 day24 day25
[clojure.test :refer [deftest is run-tests successful?]]))
Expand Down Expand Up @@ -36,6 +36,7 @@
(check-day 11 [55312 65601038650482] [183435 218279375708592])
(check-day 12 [1930 1206] [1464678 877492])
(check-day 13 [480 875318608908] [27105 101726882250942])
(check-day 14 nil [226236192 8168])


(let [summary (run-tests)]
Expand Down
1 change: 1 addition & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ Task | Notebook | Comment
[Day 11: Plutonian Pebbles](https://adventofcode.com/2024/day/11) | [day11.clj](clojure/day11) | Episode VI: Return of the Lanternfish
[Day 12: Garden Groups](https://adventofcode.com/2024/day/12) | [day12.clj](clojure/day12) | Turns = sides.
[Day 13: Claw Contraption](https://adventofcode.com/2024/day/13) | [day13.clj](clojure/day13) | Kosmo Cramer!
[Day 14: Restroom Redoubt](https://adventofcode.com/2024/day/14) | [day14.clj](clojure/day14) | Christmas egg: Easter tree.

Loading

0 comments on commit 71313cc

Please sign in to comment.