Skip to content

Commit 6105295

Browse files
day 14 article (#822)
* day 14 article * add browser support * make size more explicit * Specify that tree being solid is assumption * inline -> contiguous
1 parent c9682d6 commit 6105295

File tree

2 files changed

+306
-1
lines changed

2 files changed

+306
-1
lines changed

docs/2024/puzzles/day14.md

Lines changed: 304 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,315 @@
11
import Solver from "../../../../../website/src/components/Solver.js"
22

33
# Day 14: Restroom Redoubt
4+
by [Bulby](https://github.com/TheDrawingCoder-Gamer)
5+
46

57
## Puzzle description
68

79
https://adventofcode.com/2024/day/14
810

11+
## Solution Summary
12+
13+
1. Parse input into a `List[Robot]`
14+
2. Make function to advance state by `n` steps
15+
3. Solve
16+
* For `part1`, this is advancing the state by 100 then calculating the safety score
17+
* For `part2`, this is finding the first state where a christmas tree is visible
18+
19+
## Part 1
20+
21+
Part 1 shouldn't be too bad. Let's get started with our `Robot` class (and a `Vec2i` class):
22+
23+
```scala
24+
case class Vec2i(x: Int, y: Int)
25+
26+
case class Robot(pos: Vec2i, velocity: Vec2i)
27+
```
28+
29+
30+
Now we can parse our input:
31+
32+
```scala
33+
def parse(str: String): List[Robot] =
34+
str.linesIterator.map:
35+
case s"p=$px,$py v=$vx,$vy" =>
36+
Robot(Vec2i(px.toInt, py.toInt), Vec2i(vx.toInt, vy.toInt))
37+
.toList
38+
```
39+
40+
Let's define our grid size in a `val`, as it can depend on if we are doing a test input or not:
41+
42+
```scala
43+
val size = Vec2i(101, 103)
44+
```
45+
46+
The problem text states that when a robot goes off the edge it comes back on the other side, which sounds a lot like modulo.
47+
Unfortunately, modulo is incorrect in our case for negative numbers. Let's define a remainder instead:
48+
49+
```scala
50+
extension (self: Int)
51+
infix def rem(that: Int): Int =
52+
val m = math.abs(self) % that
53+
if self < 0 then
54+
that - m
55+
else
56+
m
57+
```
58+
59+
Now we can add a function to `Robot` that advances the state by `n`:
60+
61+
```scala
62+
case class Robot(pos: Vec2i, velocity: Vec2i):
63+
def stepN(n: Int = 1): Robot =
64+
copy(pos = pos.copy(x = (pos.x + n * velocity.x) rem size.x, y = (pos.y + n * velocity.y) rem size.y))
65+
```
66+
67+
Now for the full `List[Robot]`, we can add `stepN` to that too, and also define our safety function:
68+
69+
```scala
70+
extension (robots: List[Robot])
71+
def stepN(n: Int = 1): List[Robot] = robots.map(_.stepN(n))
72+
73+
def safety: Int =
74+
val middleX = size.x / 2
75+
val middleY = size.y / 2
76+
77+
robots.groupBy: robot =>
78+
(robot.pos.x.compareTo(middleX), robot.pos.y.compareTo(middleY)) match
79+
case (0, _) | (_, 0) => -1
80+
case ( 1, -1) => 0
81+
case (-1, -1) => 1
82+
case (-1, 1) => 2
83+
case ( 1, 1) => 3
84+
.removed(-1).values.map(_.length).product
85+
```
86+
87+
Let's explain this a little. There are 4 quadrants, and as specified there are also lines that aren't in any quadrant.
88+
We can use `groupBy` to group the robots into quadrants.
89+
90+
First, we get the midpoints by dividing the size by 2. We then compare the robot to the midpoint, returning `-1` if it's on the line
91+
(either comparison is equal), and otherwise sort the remaining 4 results into quadrants. We then remove the robots on the line,
92+
get the length of each of the lists, and multiply them together.
93+
94+
With this `part1` is easy to implement:
95+
96+
```scala
97+
def part1(input: String): Int = parse(input).stepN(100).safety
98+
```
99+
100+
## Part 2
101+
102+
Part 2 wants us to find an image which is really hard. Thankfully, there is one thing I know about Christmas trees: They have
103+
a lot of lines in a row and are an organized shape.
104+
105+
We are assuming here that the tree is solid. This assumption is fine in this case, the space to search is finite
106+
so the worst we can get is a "not found" answer, but in other problems we may want to be more sure about our input.
107+
108+
The christmas trees I know are really tall, but only in a few columns. So let's test the vertical columns for a few lines that are really long.
109+
They also have a lot of shorter horizontal lines, so let's also check the rows for a lot of shorter lines.
110+
111+
Let's also inspect the input more: the grid size is 101 wide and 103 tall. If we've moved vertically 103 times, then we've moved a multiple
112+
of 103 and are thus back at the start. The same logic applies for horizontal movement, but with 101 instead. This means a robot can, at most, be in
113+
101 * 103 unique positions, or 10,403, and because all robots are moved at the same time there will only ever be 10,403 unique states. This lets us
114+
fail fast while writing our code in case we messed something up.
115+
116+
117+
This is arbitrary and only really possible by print debugging your code, but here's my final code:
118+
119+
```scala
120+
extension (robots: List[Robot])
121+
def findEasterEgg: Int =
122+
(0 to (size.x * size.y)).find: i =>
123+
val newRobots = robots.stepN(i)
124+
newRobots.groupBy(_.pos.y).count(_._2.length >= 10) > 15 && newRobots.groupBy(_.pos.x).count(_._2.length >= 15) >= 3
125+
.getOrElse(-1)
126+
```
127+
128+
We don't even need to check if the lines are contiguous - our test is strict enough with counting lines that it works regardless. Results may vary
129+
on your input. Here I test if there are more than 15 horizontal lines with a length of 10 or more, and that there are 3 or more vertical lines
130+
with length 15 or greater.
131+
132+
Then let's hook it up to `part2`:
133+
134+
```scala
135+
def part2(input: String): Int = parse(input).findEasterEgg
136+
```
137+
138+
My Christmas tree looks like this:
139+
140+
```
141+
......................................................................................................
142+
.................................................#....................................................
143+
..............................................#.......................................................
144+
................#.#............................#......................................................
145+
.................................................................................#....................
146+
................................................................................................#.....
147+
..#...................................................................................................
148+
.....#........................................................................................#.......
149+
.#....................................................................................................
150+
......................................................................................................
151+
...........................................................#...........#........#.....................
152+
.........#..........................................#.........#.......................................
153+
..............................................#........#..............................................
154+
.......................................#..........#...................................................
155+
...................................................................................#.............#....
156+
............................................................#.........................................
157+
.................#.......................#.............................#..............................
158+
.............................#........................................................................
159+
.................................#...............................#.........................#..........
160+
.........#........................#..........................................#........................
161+
......................................................................................................
162+
.............................#........................................................................
163+
.......#.............................#....#...........................................................
164+
...#..............................................................#......................#............
165+
..........................#............................................#..............................
166+
.......................................................................................#..............
167+
..............................................#.......................................................
168+
.............#.......................................................................#................
169+
............#.................................#.....#.......................................#.........
170+
......................................................................................................
171+
................###############################.......................................................
172+
................#.............................#.......................................................
173+
................#.............................#.....#........................#........................
174+
................#.............................#.................#.....................................
175+
................#.............................#.......................................................
176+
................#..............#..............#.......................................................
177+
................#.............###.............#....#.........................................#........
178+
................#............#####............#.......................................................
179+
................#...........#######...........#.......................................................
180+
................#..........#########..........#.......................................................
181+
......#.........#............#####............#......................................................#
182+
................#...........#######...........#.......................................................
183+
................#..........#########..........#....................#...............................#..
184+
................#.........###########.........#........................................#..............
185+
...#......#.....#........#############........#..#....................................................
186+
................#..........#########..........#..............#.....................................#..
187+
................#.........###########.........#.......................................................
188+
................#........#############........#............................................#..........
189+
................#.......###############.......#.......................................................
190+
..........#.....#......#################......#.......................................................
191+
................#........#############........#..........................#.........#..................
192+
................#.......###############.......#....................................................#..
193+
........#.......#......#################......#......................................................#
194+
................#.....###################.....#.......................................................
195+
................#....#####################....#..#....................................................
196+
................#.............###.............#..........#............................................
197+
................#.............###.............#......................#................................
198+
................#.............###.............#..........................#............................
199+
................#.............................#.......................#...............................
200+
................#.............................#....................................#..................
201+
................#.............................#......................................#................
202+
......#.........#.............................#.......................................................
203+
................###############################............................#..........................
204+
......................................................................................................
205+
...............#...........................................#..........................................
206+
.........................................#............................................................
207+
...................#.........................................................#........................
208+
.....................................#.............................................................#..
209+
...........................#....................#.....................................................
210+
..........................................................#...........................#...............
211+
......................................................................................................
212+
..#............#................#............................................#........................
213+
........................................#..#..........................................................
214+
...........#......................................................................#...................
215+
............#..........#...............................................................#.#............
216+
............................................................#.........................................
217+
...#......................................................................#...........................
218+
.....#................................................................................................
219+
......................................................................................................
220+
......................................................................................................
221+
............................................#......................................#..................
222+
...............#............................................................#.........................
223+
........................................#.............................................................
224+
............#..#......................................................................................
225+
...........#.............#.................#..........................................................
226+
................................#.....................................................................
227+
.........#.................................#..........................................................
228+
.......................#............................................................................#.
229+
...............#......................................................................................
230+
..............................#.#.....................................................................
231+
......................................................................................................
232+
......................................................................................................
233+
...........#..........................................................................................
234+
.........................................................................................#............
235+
..........................#...........................................................................
236+
...............................#.......................#..............................................
237+
...............#......................................................................................
238+
...........#...............................#..........................................................
239+
.................#.................#............................................#...#.................
240+
...................................................................................................#..
241+
...............................................................................#......................
242+
....#...................................................................#.............................
243+
........#...................#.....................#...................................................
244+
................................................................................#.....................
245+
```
246+
247+
With this information of how this looks we could make a smarter `findEasterEgg` with the knowledge of the border. The border
248+
makes it much easier as we only have to check for 2 contiguous lines in each dimension.
249+
250+
## Final Code
251+
252+
```scala
253+
case class Vec2i(x: Int, y: Int)
254+
255+
val size = Vec2i(101, 103)
256+
257+
extension (self: Int)
258+
infix def rem(that: Int): Int =
259+
val m = math.abs(self) % that
260+
if self < 0 then
261+
that - m
262+
else
263+
m
264+
265+
case class Robot(pos: Vec2i, velocity: Vec2i)
266+
def stepN(n: Int = 1): Robot =
267+
copy(pos = pos.copy(x = (pos.x + n * velocity.x) rem size.x, y = (pos.y + n * velocity.y) rem size.y))
268+
269+
def parse(str: String): List[Robot] =
270+
str.linesIterator.map:
271+
case s"p=$px,$py v=$vx,$vy" =>
272+
Robot(Vec2i(px.toInt, py.toInt), Vec2i(vx.toInt, vy.toInt))
273+
.toList
274+
275+
extension (robots: List[Robot])
276+
def stepN(n: Int = 1): List[Robot] = robots.map(_.stepN(n))
277+
278+
def safety: Int =
279+
val middleX = size.x / 2
280+
val middleY = size.y / 2
281+
282+
robots.groupBy: robot =>
283+
(robot.pos.x.compareTo(middleX), robot.pos.y.compareTo(middleY)) match
284+
case (0, _) | (_, 0) => -1
285+
case ( 1, -1) => 0
286+
case (-1, -1) => 1
287+
case (-1, 1) => 2
288+
case ( 1, 1) => 3
289+
.removed(-1).values.map(_.length).product
290+
291+
def findEasterEgg: Int =
292+
(0 to 10403).find: i =>
293+
val newRobots = robots.stepN(i)
294+
newRobots.groupBy(_.pos.y).count(_._2.length >= 10) > 15 && newRobots.groupBy(_.pos.x).count(_._2.length >= 15) >= 3
295+
.getOrElse(-1)
296+
297+
def part1(input: String): Int = parse(input).stepN(100).safety
298+
299+
def part2(input: String): Int = parse(input).findEasterEgg
300+
```
301+
302+
## Run it in the browser
303+
304+
### Part 1
305+
306+
<Solver puzzle="day14-part1" year="2024"/>
307+
308+
### Part 2
309+
310+
<Solver puzzle="day14-part2" year="2024"/>
311+
312+
9313
## Solutions from the community
10314

11315
- [Solution](https://github.com/nikiforo/aoc24/blob/main/src/main/scala/io/github/nikiforo/aoc24/D14T2.scala) by [Artem Nikiforov](https://github.com/nikiforo)
@@ -16,7 +320,6 @@ https://adventofcode.com/2024/day/14
16320
- [Solution](https://github.com/spamegg1/aoc/blob/master/2024/14/14.scala#L165) by [Spamegg](https://github.com/spamegg1)
17321
- [Solution](https://github.com/jnclt/adventofcode2024/blob/main/day14/restroom-redoubt.sc) by [jnclt](https://github.com/jnclt)
18322
- [Solution](https://github.com/Philippus/adventofcode/blob/main/src/main/scala/adventofcode2024/Day14.scala) by [Philippus Baalman](https://github.com/philippus)
19-
- [Writeup](https://thedrawingcoder-gamer.github.io/aoc-writeups/2024/day14.html) by [Bulby](https://github.com/TheDrawingCoder-Gamer)
20323
- [Solution](https://github.com/jportway/advent2024/blob/master/src/main/scala/Day14.scala) by [Joshua Portway](https://github.com/jportway)
21324
- [Solution](https://github.com/AvaPL/Advent-of-Code-2024/tree/main/src/main/scala/day14) by [Paweł Cembaluk](https://github.com/AvaPL)
22325

solver/src/main/scala/adventofcode/Solver.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ object Solver:
1111
Map(
1212
"day13-part1" -> day13.part1,
1313
"day13-part2" -> day13.part2,
14+
"day14-part1" -> day14.part1,
15+
"day14-part2" -> day14.part2,
1416
"day21-part1" -> day21.part1,
1517
"day21-part2" -> day21.part2,
1618
"day22-part1" -> day22.part1,

0 commit comments

Comments
 (0)