1
1
import scala .io .Source
2
2
import scala .collection .mutable
3
3
4
- case class Vec2 (x : Int , y : Int ) {
5
- def neighbors = (- 1 to 1 ).flatMap { dy =>
6
- (- 1 to 1 )
7
- .filter { dx => (dx != 0 ) ^ (dy != 0 ) }
8
- .map { dx => new Vec2 (x + dx, y + dy) }
9
- }
10
-
11
- def + (that : Vec2 ) = Vec2 (x + that.x, y + that.y)
12
-
13
- def manhattanDist (that : Vec2 ) = (x - that.x).abs + (y - that.y).abs
14
- }
15
-
16
- enum PadType :
17
- case Num , Dir
18
-
19
- val PAD_LAYOUTS = Map (
20
- (PadType .Num , Map (
21
- (Vec2 (0 , 0 ), '7' ), (Vec2 (1 , 0 ), '8' ), (Vec2 (2 , 0 ), '9' ),
22
- (Vec2 (0 , 1 ), '4' ), (Vec2 (1 , 1 ), '5' ), (Vec2 (2 , 1 ), '6' ),
23
- (Vec2 (0 , 2 ), '1' ), (Vec2 (1 , 2 ), '2' ), (Vec2 (2 , 2 ), '3' ),
24
- (Vec2 (1 , 3 ), '0' ), (Vec2 (2 , 3 ), 'A' ),
25
- )),
26
- (PadType .Dir , Map (
27
- (Vec2 (1 , 0 ), '^' ), (Vec2 (2 , 0 ), 'A' ),
28
- (Vec2 (0 , 1 ), '<' ), (Vec2 (1 , 1 ), 'v' ), (Vec2 (2 , 1 ), '>' ),
29
- )),
30
- )
31
-
32
- extension (ptype : PadType ) {
33
- def layout = PAD_LAYOUTS (ptype)
34
-
35
- def locate (c : Char ) = ptype.layout.find(_._2 == c).get._1
36
-
37
- def shortestPath (startPos : Vec2 , endPos : Vec2 ): String =
38
- case class Node (pos : Vec2 , index : Int , program : String = " " ) extends Ordered [Node ] {
39
- def cost = program.length * 1_000_000 + turns * 1_000 + index
40
-
41
- def turns = program.zip(program.tail).count { case (c1, c2) => c1 != c2 }
42
-
43
- def compare (that : Node ): Int = that.cost compare cost // Intentionally reversed for min-heap
44
- }
45
-
46
- val queue = mutable.PriorityQueue [Node ]()
47
- val visited = mutable.Set [Vec2 ]()
48
- var index = 0
49
-
50
- queue.enqueue(Node (startPos, index))
51
-
52
- while ! queue.isEmpty do
53
- val node = queue.dequeue()
54
- if node.pos == endPos then
55
- // Annoying edge cases, what rule do they follow?
56
- return Map (
57
- (" >>^" , " ^>>" ),
58
- (" vv<" , " <vv" ),
59
- (" vv>v" , " >vvv" ),
60
- (" >>v" , " v>>" ),
61
- (" vvv<" , " <vvv" ),
62
- (" vv<" , " <vv" ),
63
- (" <^<" , " ^<<" ),
64
- (" ^^<" , " <^^" ),
65
- (" ^^^<" , " <^^^" ),
66
- (" <v<" , " v<<" ),
67
- (" <^^" , " ^<^" ),
68
- (" <^^^" , " ^^^<" ),
69
- (" v>>" , " >>v" ),
70
- (" ^>>" , " >>^" ),
71
- ).getOrElse(node.program, node.program)
72
- .appended('A' )
73
-
74
- for action <- List ('<' , '^' , 'v' , '>' ) do
75
- val dir = DIRECTIONS (action)
76
- val neigh = node.pos + dir
77
- if layout.contains(neigh) && ! visited.contains(neigh) then
78
- visited.add(neigh)
79
- queue.enqueue(Node (neigh, index, node.program.appended(action)))
80
- index += 1
81
-
82
- throw RuntimeException (" No shortest program found" )
83
-
84
- def shortestPaths : Map [(Char , Char ), String ] =
85
- layout.flatMap { case (p1, a1) => layout.map { case (p2, a2) => ((a1, a2), shortestPath(p1, p2)) } }.toMap
86
- }
87
-
88
- val DIRECTIONS = Map (
89
- ('<' , Vec2 (- 1 , 0 )),
90
- ('>' , Vec2 ( 1 , 0 )),
91
- ('^' , Vec2 ( 0 , - 1 )),
92
- ('v' , Vec2 ( 0 , 1 )),
93
- )
94
-
95
- val ACTIONS = List ('A' ) ++ DIRECTIONS .keySet
96
-
97
- case class Pad (ptype : PadType , pos : Vec2 ) {
98
- def layout = ptype.layout
99
-
100
- def activate : Char = layout(pos)
101
-
102
- def isValid = layout.contains(pos)
103
-
104
- def perform (action : Char ): (Option [Char ], Pad ) =
105
- action match
106
- case 'A' => (Some (activate), this )
107
- case _ => (None , Pad (ptype, pos + DIRECTIONS (action)))
108
- }
109
-
110
- object Pad {
111
- def apply (ptype : PadType ): Pad = Pad (ptype, ptype.locate('A' ))
112
- }
113
-
114
- def shortestProgram (robots : Int , goal : String ): String =
115
- case class State (pads : List [Pad ] = List .fill(robots)(Pad (PadType .Dir )) :+ Pad (PadType .Num ), output : String = " " ) {
116
- def perform (action : Char ) =
117
- for
118
- (newPads, outAction) <- pads.foldLeft[Option [(List [Pad ], Option [Char ])]](Some ((List (), Some (action)))) { (acc, pad) =>
119
- acc.flatMap { case (pads, action) =>
120
- action match
121
- case Some (action) =>
122
- for
123
- (newAction, newPad) <- Some (pad.perform(action))
124
- if newPad.isValid
125
- yield (pads :+ newPad, newAction)
126
- case None => Some ((pads :+ pad, None ))
127
- }
128
- }
129
- yield
130
- val newOutput = outAction.map(output.appended(_)).getOrElse(output)
131
- State (newPads, newOutput)
132
- }
133
-
134
- case class Node (state : State = State (), program : String = " " ) extends Ordered [Node ] {
135
- def compare (that : Node ): Int = that.program.length compare program.length // Intentionally reversed for min-heap
136
- }
137
-
138
- // Your run-of-the-mill Dijkstra implementation
139
-
140
- val queue = mutable.PriorityQueue [Node ]()
141
- val visited = mutable.Set [State ]()
142
-
143
- val startState = State ()
144
- val start = Node (startState)
145
- queue.enqueue(start)
146
- visited.add(startState)
147
-
148
- while ! queue.isEmpty do
149
- val node = queue.dequeue()
150
- if node.state.output == goal then
151
- return node.program
152
-
153
- if node.state.output.length < goal.length then
154
- for
155
- action <- ACTIONS
156
- newState <- node.state.perform(action)
157
- do
158
- if ! visited.contains(newState) then
159
- visited.add(newState)
160
- queue.enqueue(Node (newState, node.program.appended(action)))
161
-
162
- throw RuntimeException (" No shortest program found" )
163
-
164
- def shortestProgramLength (robots : Int , goal : String ): Int =
165
- case class State (pos : Vec2 = PadType .Num .locate('A' ), dPos : Vec2 = PadType .Dir .locate('A' ), output : String = " " )
166
-
167
- case class Node (state : State , total : Int = 0 ) extends Ordered [Node ] {
168
- def compare (that : Node ): Int = that.total compare total // Intentionally reversed for min-heap
169
- }
170
-
171
- def cost (robots : Int , pos : Vec2 , dPos : Vec2 , action : Char ): (Int , Vec2 ) =
172
- if robots <= 0 then
173
- (1 , dPos)
174
- else
175
- // Only considering robots = 1 for now
176
- val targetDPos = PadType .Dir .locate(action)
177
- val steps = dPos.manhattanDist(targetDPos) + 1 // needs 'A' press
178
- // println(s"$dPos -> $targetDPos ('${PAD_LAYOUTS(PadType.Dir)(dPos)}' ${steps} -> '${PAD_LAYOUTS(PadType.Dir)(targetDPos)}')")
179
- (steps, targetDPos)
180
-
181
- // Your run-of-the-mill Dijkstra implementation (this time on the numpad)
182
-
183
- val queue = mutable.PriorityQueue [Node ]()
184
- val visited = mutable.Set [State ]()
185
-
186
- val startState = State ()
187
- val start = Node (startState)
188
- queue.enqueue(start)
189
- visited.add(startState)
190
-
191
- while ! queue.isEmpty do
192
- val node = queue.dequeue()
193
- if node.state.output == goal then
194
- return node.total
195
-
196
- if node.state.output.length < goal.length then
197
- for
198
- action <- ACTIONS
199
- do
200
- val newPos = node.state.pos + DIRECTIONS .get(action).getOrElse(Vec2 (0 , 0 ))
201
- if PAD_LAYOUTS (PadType .Num ).contains(newPos) then
202
- val (c, newDPos) = cost(robots, node.state.pos, node.state.dPos, action)
203
- val newOutput = if action == 'A' then node.state.output.appended(PAD_LAYOUTS (PadType .Num )(node.state.pos)) else node.state.output
204
- val newState = State (newPos, newDPos, newOutput)
205
- if ! visited.contains(newState) then
206
- visited.add(newState)
207
- queue.enqueue(Node (newState, node.total + c))
208
-
209
- throw RuntimeException (" No shortest program found" )
210
-
211
- def solve (robots : Int , goals : List [String ], func : (Int , String ) => Long ): Long =
212
- goals.map { goal =>
213
- val shortest = func(robots, goal)
214
- shortest * goal.dropRight(1 ).toLong
215
- }.sum
216
-
217
- // Algorithm/approach is effectively a Scala port of
4
+ // The algorithm/approach is effectively a Scala port of
218
5
// https://www.reddit.com/r/adventofcode/comments/1hj2odw/comment/m36j01x For
219
6
// some reason my Dijkstra-based shortest-path constructions didn't yield the
220
- // specific/right ordering, so I eventually just ended up using the hardcoded
221
- // map too. Most of the experiments can be found in earlier commits.
7
+ // specific/right ordering, so, after long (and frustrating) experimentation, I
8
+ // eventually just ended up using that hardcoded map too. Most of the
9
+ // experiments can be found in earlier commits.
222
10
223
11
val SHORTEST_PATHS = Map (
224
12
(('A' , '0' ), " <A" ),
@@ -355,7 +143,7 @@ val SHORTEST_PATHS = Map(
355
143
356
144
val memo = mutable.Map [(Int , String ), Long ]()
357
145
358
- def shortestProgramLengthClever (robots : Int , goal : String ): Long =
146
+ def shortestProgramLength (robots : Int , goal : String ): Long =
359
147
val key = (robots, goal)
360
148
if memo.contains(key) then
361
149
return memo(key)
@@ -369,29 +157,24 @@ def shortestProgramLengthClever(robots: Int, goal: String): Long =
369
157
length += moveCount(robots, current, next)
370
158
current = next
371
159
length
160
+
372
161
memo(key) = result
373
162
result
374
163
375
164
def moveCount (robots : Int , current : Char , next : Char ): Long =
376
165
if current == next then
377
166
1
378
167
else
379
- shortestProgramLengthClever(robots - 1 , SHORTEST_PATHS ((current, next)))
168
+ shortestProgramLength(robots - 1 , SHORTEST_PATHS ((current, next)))
169
+
170
+ def solve (robots : Int , goals : List [String ]): Long =
171
+ goals.map { goal =>
172
+ val shortest = shortestProgramLength(robots, goal)
173
+ shortest * goal.dropRight(1 ).toLong
174
+ }.sum
380
175
381
176
@ main def main (path : String ) =
382
177
val goals = Source .fromFile(path).getLines.toList
383
- // println(s"Part 1: ${solve(2, goals)}")
384
- // println(s"Part 2: ${solve(25, goals)}")
385
-
386
- // for (k, p) <- SHORTEST_PATHS.toList.sorted do
387
- // if k._1 != k._2 && p != SHORTEST_PATHS_2(k) then
388
- // println(s"(\"$p\", \"${SHORTEST_PATHS_2(k)}\")")
389
-
390
- println(s " Part 1: ${solve(2 , goals, shortestProgramLengthClever)}" )
391
- println(s " Part 2: ${solve(25 , goals, shortestProgramLengthClever)}" )
392
-
393
- // for i <- (0 to 3) do
394
- // println(s"${solve(i, goals, { (r, g) => shortestProgram(r, g).length })} vs ${solve(i, goals, shortestProgramLength)} vs ${solve(i, goals, shortestProgramLengthClever)}")
395
178
396
- // for c <- ('0' to '5') do
397
- // println(s"$c -> ${(0 to 3).map { i => shortestProgram(makeState(i), s"$c").length } }")
179
+ println( s " Part 1: ${solve( 2 , goals)} " )
180
+ println(s " Part 2: ${solve( 25 , goals) }" )
0 commit comments