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/day17.md
+265Lines changed: 265 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -6,8 +6,273 @@ import Solver from "../../../../../website/src/components/Solver.js"
6
6
7
7
https://adventofcode.com/2023/day/17
8
8
9
+
## Solution Summary
10
+
11
+
This is a classic search problem with an interesting restriction on state transformations.
12
+
13
+
We will solve this using Djikstra's Algorithm to find a path through the grid, using the heat loss of each position as our node weights. However, the states in our priority queue will need to include more than just position and accumulated heat loss, since the streak of forward movements in a given direction affects which positions are accessible from a given state.
14
+
15
+
Since the restrictions on state transformations differ in part 1 and part 2, we'll model them separately from the base state transformations.
16
+
17
+
### Framework
18
+
19
+
First, for convenience, let's introduce classes for presenting position and direction:
And now a few convenience methods that need the input:
59
+
```scala
60
+
valxRange= grid.head.indices
61
+
valyRange= grid.indices
62
+
63
+
definBounds(p: Point) =
64
+
xRange.contains(p.x) && yRange.contains(p.y)
65
+
66
+
defheatLoss(p: Point) =
67
+
if inBounds(p) then grid(p.y)(p.x) else0
68
+
```
69
+
70
+
### Search State
71
+
72
+
Now we want to be able to model our state as we're searching. The state will track our position. To know what transitions are possible, we need to keep track of our streak of movements in a given direction. We'll also keep track of the heat lost while getting to a state.
73
+
74
+
```scala
75
+
caseclassState(pos: Point, dir: Dir, streak: Int):
76
+
```
77
+
78
+
Next let's define some methods for transitioning to new states. We know that we can chose to move forward, turn left, or turn right. For now, we won't consider the restrictions from Part 1 or Part 2 on whether or not you can move forward:
79
+
80
+
```scala
81
+
// inside case class State:
82
+
defstraight:State=
83
+
valnewPos= pos.move(dir)
84
+
State(pos.move(dir), dir, streak +1)
85
+
86
+
defturnLeft:State=
87
+
valnewDir= dir.turnLeft
88
+
valnewPos= pos.move(newDir)
89
+
State(newPos, newDir, 1)
90
+
91
+
defturnRight:State=
92
+
valnewDir= dir.turnRight
93
+
valnewPos= pos.move(newDir)
94
+
State(newPos, newDir, 1)
95
+
```
96
+
97
+
Note that the streak resets to one when we turn right or turn left, since we also move the position forward in that new direction.
98
+
99
+
### Djikstra's Algorithm
100
+
101
+
Finally, let's lay the groundwork for an implementation of Djikstra's algorithm.
102
+
103
+
Since our valid state transformations vary between part 1 and part 2, let's parameterize our search method by a function:
104
+
105
+
```scala
106
+
defsearch(next: State=>List[State]):Int
107
+
```
108
+
109
+
The algorithm uses Map to track the minimum total heat loss for each state, and a Priority Queue prioritizing by this heatloss to choose the next state to visit:
110
+
111
+
```scala
112
+
// inside def search:
113
+
importcollection.mutable.{PriorityQueue, Map}
114
+
115
+
valminHeatLoss=Map.empty[State, Int]
116
+
117
+
givenOrdering[State] =Ordering.by(minHeatLoss)
118
+
valpq=PriorityQueue.empty[State].reverse
119
+
120
+
varvisiting=State(Point(0, 0), Dir.E, 0)
121
+
val minHeatLoss(visiting) =0
122
+
```
123
+
124
+
As we generate new states to add to the priority Queue, we need to make sure not to add suboptimal states. The first time we visit any state, it will be with a minimum possible cost, because we're visiting this new state from an adjacent minimum heatloss state in our priority queue.
125
+
So any state we've already visited will be discarded. This is what our loop will look like:
Notice how minHeatLoss is always updated to the minimum of the state we're visiting from plus the incremental heatloss of the new state we're adding to the queue.
140
+
141
+
### Part 1
142
+
143
+
Now we need to model our state transformation restrictions for Part 1. We can typically move straight, left, and right, but we need to make sure our streak straight streak never exceeds 3:
144
+
145
+
```scala
146
+
// Inside case class State:
147
+
defnextStates:List[State] =
148
+
List(straight, turnLeft, turnRight).filter: s =>
149
+
inBounds(s.pos) && s.streak <=3
150
+
```
151
+
152
+
This will only ever filter out the forward movement, since moving to the left or right resets the streak to 1.
153
+
154
+
### Part 2
155
+
156
+
Part 2 is similar, but our streak limit increases to 10.
157
+
Furthermore, while the streak is less than four, only a forward movement is possible:
158
+
159
+
```scala
160
+
// inside case class State:
161
+
defnextStates2:List[State] =
162
+
if streak <4thenList(straight)
163
+
elseList(straight, turnLeft, turnRight).filter: s =>
164
+
inBounds(s.pos) && s.streak <=10
165
+
```
166
+
167
+
## Final Code
168
+
169
+
```scala
170
+
importlocations.Directory.currentDir
171
+
importinputs.Input.loadFileSync
172
+
173
+
@main defpart1:Unit=
174
+
println(s"The solution is ${search(_.nextStates)}")
175
+
176
+
@main defpart2:Unit=
177
+
println(s"The solution is ${search(_.nextStates2)}")
-[Solution](https://github.com/stewSquared/advent-of-code/blob/master/src/main/scala/2021/Day17.worksheet.sc) by [stewSquared](https://github.com/stewSquared)
11
276
-[Solution](https://github.com/merlinorg/aoc2023/blob/main/src/main/scala/Day17.scala) by [merlin](https://github.com/merlinorg/)
12
277
-[Solution](https://github.com/xRuiAlves/advent-of-code-2023/blob/main/Day17.scala) by [Rui Alves](https://github.com/xRuiAlves/)
0 commit comments