You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/2023/puzzles/day23.md
+49-24Lines changed: 49 additions & 24 deletions
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,6 @@
1
1
import Solver from "../../../../../website/src/components/Solver.js"
2
2
import Literate from "../../../../../website/src/components/Literate.js"
3
+
import ExpandImage from "../../../../../website/src/components/ExpandImage.js"
3
4
4
5
# Day 23: A Long Walk
5
6
@@ -25,7 +26,13 @@ Each junction might have two or three adjacent paths we can enter. When we exit
25
26
26
27
- For each iteration of the search through this graph, we check all adjacent junctions against the visited set. When using a hash set, this will result in computing hashes of position coordinates tens of millions of times. We can avoid this by giving each junction an index and using a BitSet of these indices as our visited set. Checking for membership in a BitSet only requires a bitshift and a bitwise AND mask. On my machine, this drops the run time from ~7 seconds to ~2 seconds (70%).
27
28
28
-
For part 1, neither of these optimizations are necessary. To understand why, notice that every junction is surrounded by slopes. When a junction is surrounded by four slopes, as most of them are, two are incoming and two are outgoing. For part 1, these are arranged in such a way that the adjacency graph becomes a directed acyclic graph, with a greatly reduced search space. One way to notice this early on is to generate a [visualization via GraphViz](https://dreampuf.github.io/GraphvizOnline/#digraph%20G%20%7B%0A%220%22%20-%3E%20%221%22%20%5Blabel%3D159%5D%3B%0A%221%22%20-%3E%20%223%22%20%5Blabel%3D138%5D%3B%0A%221%22%20-%3E%20%222%22%20%5Blabel%3D80%5D%3B%0A%222%22%20-%3E%20%224%22%20%5Blabel%3D170%5D%3B%0A%222%22%20-%3E%20%225%22%20%5Blabel%3D310%5D%3B%0A%223%22%20-%3E%20%224%22%20%5Blabel%3D72%5D%3B%0A%223%22%20-%3E%20%226%22%20%5Blabel%3D334%5D%3B%0A%224%22%20-%3E%20%2211%22%20%5Blabel%3D232%5D%3B%0A%224%22%20-%3E%20%229%22%20%5Blabel%3D116%5D%3B%0A%225%22%20-%3E%20%2211%22%20%5Blabel%3D184%5D%3B%0A%225%22%20-%3E%20%227%22%20%5Blabel%3D112%5D%3B%0A%226%22%20-%3E%20%229%22%20%5Blabel%3D38%5D%3B%0A%226%22%20-%3E%20%228%22%20%5Blabel%3D44%5D%3B%0A%227%22%20-%3E%20%2212%22%20%5Blabel%3D222%5D%3B%0A%227%22%20-%3E%20%2213%22%20%5Blabel%3D66%5D%3B%0A%228%22%20-%3E%20%2210%22%20%5Blabel%3D38%5D%3B%0A%228%22%20-%3E%20%2215%22%20%5Blabel%3D440%5D%3B%0A%229%22%20-%3E%20%2214%22%20%5Blabel%3D188%5D%3B%0A%229%22%20-%3E%20%2210%22%20%5Blabel%3D20%5D%3B%0A%2210%22%20-%3E%20%2216%22%20%5Blabel%3D120%5D%3B%0A%2210%22%20-%3E%20%2219%22%20%5Blabel%3D222%5D%3B%0A%2211%22%20-%3E%20%2214%22%20%5Blabel%3D72%5D%3B%0A%2211%22%20-%3E%20%2213%22%20%5Blabel%3D74%5D%3B%0A%2212%22%20-%3E%20%2218%22%20%5Blabel%3D144%5D%3B%0A%2212%22%20-%3E%20%2220%22%20%5Blabel%3D520%5D%3B%0A%2213%22%20-%3E%20%2217%22%20%5Blabel%3D202%5D%3B%0A%2213%22%20-%3E%20%2218%22%20%5Blabel%3D140%5D%3B%0A%2214%22%20-%3E%20%2216%22%20%5Blabel%3D196%5D%3B%0A%2214%22%20-%3E%20%2217%22%20%5Blabel%3D152%5D%3B%0A%2215%22%20-%3E%20%2219%22%20%5Blabel%3D184%5D%3B%0A%2215%22%20-%3E%20%2222%22%20%5Blabel%3D472%5D%3B%0A%2216%22%20-%3E%20%2223%22%20%5Blabel%3D94%5D%3B%0A%2216%22%20-%3E%20%2221%22%20%5Blabel%3D198%5D%3B%0A%2217%22%20-%3E%20%2224%22%20%5Blabel%3D56%5D%3B%0A%2217%22%20-%3E%20%2221%22%20%5Blabel%3D102%5D%3B%0A%2218%22%20-%3E%20%2224%22%20%5Blabel%3D146%5D%3B%0A%2218%22%20-%3E%20%2220%22%20%5Blabel%3D60%5D%3B%0A%2219%22%20-%3E%20%2222%22%20%5Blabel%3D60%5D%3B%0A%2219%22%20-%3E%20%2223%22%20%5Blabel%3D40%5D%3B%0A%2220%22%20-%3E%20%2228%22%20%5Blabel%3D280%5D%3B%0A%2221%22%20-%3E%20%2227%22%20%5Blabel%3D162%5D%3B%0A%2221%22%20-%3E%20%2226%22%20%5Blabel%3D82%5D%3B%0A%2222%22%20-%3E%20%2225%22%20%5Blabel%3D124%5D%3B%0A%2223%22%20-%3E%20%2226%22%20%5Blabel%3D190%5D%3B%0A%2223%22%20-%3E%20%2225%22%20%5Blabel%3D112%5D%3B%0A%2224%22%20-%3E%20%2227%22%20%5Blabel%3D104%5D%3B%0A%2224%22%20-%3E%20%2228%22%20%5Blabel%3D250%5D%3B%0A%2225%22%20-%3E%20%2230%22%20%5Blabel%3D336%5D%3B%0A%2226%22%20-%3E%20%2231%22%20%5Blabel%3D128%5D%3B%0A%2226%22%20-%3E%20%2230%22%20%5Blabel%3D98%5D%3B%0A%2227%22%20-%3E%20%2229%22%20%5Blabel%3D156%5D%3B%0A%2227%22%20-%3E%20%2231%22%20%5Blabel%3D132%5D%3B%0A%2228%22%20-%3E%20%2229%22%20%5Blabel%3D58%5D%3B%0A%2229%22%20-%3E%20%2232%22%20%5Blabel%3D392%5D%3B%0A%2230%22%20-%3E%20%2233%22%20%5Blabel%3D188%5D%3B%0A%2231%22%20-%3E%20%2233%22%20%5Blabel%3D62%5D%3B%0A%2231%22%20-%3E%20%2232%22%20%5Blabel%3D84%5D%3B%0A%2232%22%20-%3E%20%2234%22%20%5Blabel%3D92%5D%3B%0A%2233%22%20-%3E%20%2234%22%20%5Blabel%3D158%5D%3B%0A%2234%22%20-%3E%20%2235%22%20%5Blabel%3D49%5D%3B%0A%7D).
29
+
For part 1, neither of these optimizations are necessary. To understand why, notice that every junction is surrounded by slopes. When a junction is surrounded by four slopes, as most of them are, two are incoming and two are outgoing. For part 1, these are arranged in such a way that the adjacency graph becomes a directed acyclic graph, with a greatly reduced search space. One way to notice this early on is to generate a visualization via GraphViz, such as the following:
30
+
31
+
import GraphVizSvg from '/img/2023-day23/graphviz.svg';
32
+
33
+
<ExpandImage>
34
+
<GraphVizSvg />
35
+
</ExpandImage>
29
36
30
37
### Framework
31
38
@@ -112,15 +119,19 @@ Next, we need an algorithm for finding junctions that are connected to a given j
This `walk` helper method attempts to move in a given direction from a given position, accounting for walls and slopes in the maze. This alternatively could have been defined as a method on `Point` itself.
This `walk` helper method attempts to move in a given direction from a given position, accounting for walls and slopes in the maze. This alternatively could have been defined as a method on `Point` itself.
134
+
This `search` helper method walks down a path from a junction while tracking the current direction and distance. `adjacentSearch`attempts to walk recursively in directions that don't go backwards. A LazyList is used here to prevent stack overflows. If there is only one adjacent path to walk too, we continue searching that path recursively until we reach a junction, otherwise, we have reached a dead end; `None` represents the fact that no new junctions are reachable down this path.
124
135
125
136
```scala
126
137
defsearch(pos: Point, facing: Dir, dist: Int):Option[(Point, Int)] =
@@ -133,33 +144,40 @@ This `walk` helper method attempts to move in a given direction from a given pos
133
144
if adjacentSearch.size ==1then adjacentSearch.head elseNone
134
145
```
135
146
136
-
This `search` helper method walks down a path from a junction while tracking the current direction and distance. `adjacentSearch` attempts to walk recursively in directions that don't go backwards. A LazyList is used here to prevent stack overflows. If there is only one adjacent path to walk too, we continue searching that path recursively until we reach a junction, otherwise, we have reached a dead end; `None` represents the fact that no new junctions are reachable down this path.
147
+
Finally, we begin the search in each direction from our current junction, returning all the connected junctions found.
137
148
138
149
```scala
139
150
for
140
151
d <-Dir.values
141
152
p <- walk(pos, d)
142
153
junction <- search(p, d, 1)
143
154
yield junction
144
-
155
+
endconnectedJunctions
145
156
```
146
157
147
-
Finally, we begin the search in each direction from our current junction, returning all the connected junctions found.
148
-
149
158
</Literate>
150
159
151
160
### Part 1
152
161
153
162
`connectedJunctions` is sufficient to solve Part 1 quickly:
defsearch(pos: Point, dist: Int)(usingmaze: Maze):Int=
174
+
defsearch(pos: Point, dist: Int):Int=
158
175
if pos == maze.end then dist else
159
176
connectedJunctions(pos).foldLeft(0):
160
177
case (max, (n, d)) => max.max(search(n, dist + d))
161
178
162
179
search(maze.start, 0)
180
+
endlongestDownhillHike
163
181
```
164
182
165
183
This uses a recursive helper method named `search`. Beginning with `start`, we recursively search for the longest path starting at each of the connected junctions.
@@ -170,17 +188,23 @@ For part 2, we'll implement the optimization mentioned in the overview, namely,
170
188
171
189
<Literate>
172
190
173
-
We begin by assigning indices to each of the junctions, by sorting them (in any way, as long as the ordering is well-defined) and zipping with an index:
174
-
175
191
```scala
192
+
defpart2(input: String):Int=
193
+
givenMaze=Maze(parseInput(input))
194
+
longestHike
195
+
176
196
deflongestHike(usingmaze: Maze):Int=
177
197
typeIndex=Int
198
+
```
199
+
200
+
We begin by assigning indices to each of the junctions, by sorting them (in any way, as long as the ordering is well-defined) and zipping with an index:
Next, we define an adjacency graph. Since `connectedJunctinos` takes slopes into account, and we no longer care about slopes for part 2, we add both the forward and reverse directinos into our Map. Note how we translate the Point locations used by `connectedJunctions` into indices using `indexOf`, defined above:
207
+
Next, we define an adjacency graph. Since `connectedJunctions` takes slopes into account, and we no longer care about slopes for part 2, we add both the forward and reverse directions into our Map. Note how we translate the Point locations used by `connectedJunctions` into indices using `indexOf`, defined above:
184
208
185
209
```scala
186
210
valadjacent:Map[Index, List[(Index, Int)]] =
@@ -193,7 +217,7 @@ Next, we define an adjacency graph. Since `connectedJunctinos` takes slopes into
193
217
```
194
218
195
219
Finally, we perform a depth-first search that is very similar to what we used in Part 1.
196
-
The main differences are that we now use indices of junctions rather than `Point`s reperesenting current position, and we now check adjacent junctions against a BitSet of visited points, which we now track as we search recursively.
220
+
The main differences are that we now use indices of junctions rather than `Point`s representing current position, and we now check adjacent junctions against a BitSet of visited points, which we now track as we search recursively.
0 commit comments