@@ -31,145 +31,154 @@ object Day21 {
31
31
'<' -> Pos (- 1 , 0 ),
32
32
)
33
33
34
- case class State (directionalPoss : List [Pos ], numericPos : Pos , input : Code ) {
35
-
36
- def numericPress (button : Char ): Option [State ] = button match {
37
- case 'A' =>
38
- val newButton = numericKeypad(numericPos)
39
- Some (copy(input = input + newButton))
40
- case _ =>
41
- val offset = directionalOffsets(button)
42
- val newNumericPos = numericPos + offset
43
- if (numericKeypad.containsPos(newNumericPos) && numericKeypad(newNumericPos) != ' ' )
44
- Some (copy(numericPos = newNumericPos))
45
- else
46
- None // out of keypad
47
- }
34
+ trait Solution {
35
+ def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long
48
36
49
- def directionalPress (button : Char ): Option [State ] = directionalPoss match {
50
- case Nil => numericPress(button)
51
- case directionalPos :: newDirectionalPoss =>
52
- button match {
53
- case 'A' =>
54
- val newButton = directionalKeypad(directionalPos)
55
- copy(directionalPoss = newDirectionalPoss).directionalPress(newButton).map(newState =>
56
- newState.copy(directionalPoss = directionalPos :: newState.directionalPoss)
57
- )
58
- case _ =>
59
- val offset = directionalOffsets(button)
60
- val newDirectionalPos = directionalPos + offset
61
- if (directionalKeypad.containsPos(newDirectionalPos) && directionalKeypad(newDirectionalPos) != ' ' )
62
- Some (copy(directionalPoss = newDirectionalPos :: newDirectionalPoss))
63
- else
64
- None // out of keypad
65
- }
37
+ def codeComplexity (code : Code , directionalKeypads : Int ): Long = {
38
+ val numericPart = code.dropRight(1 ).toInt
39
+ shortestSequenceLength(code, directionalKeypads) * numericPart
66
40
}
67
41
68
- def userPress ( button : Char ): Option [ State ] = directionalPress(button)
42
+ def sumCodeComplexity ( codes : Seq [ Code ], directionalKeypads : Int ): Long = codes.map(codeComplexity(_, directionalKeypads)).sum
69
43
}
70
44
71
- def shortestSequenceLength (code : Code ): Int = {
72
-
73
- val graphSearch = new GraphSearch [State ] with UnitNeighbors [State ] {
74
- override val startNode : State = State (List .fill(2 )(directionalKeypad.posOf('A' )), numericKeypad.posOf('A' ), " " )
45
+ object NaiveSolution extends Solution {
46
+
47
+ case class State (directionalPoss : List [Pos ], numericPos : Pos , input : Code ) {
48
+
49
+ def numericPress (button : Char ): Option [State ] = button match {
50
+ case 'A' =>
51
+ val newButton = numericKeypad(numericPos)
52
+ Some (copy(input = input + newButton))
53
+ case _ =>
54
+ val offset = directionalOffsets(button)
55
+ val newNumericPos = numericPos + offset
56
+ if (numericKeypad.containsPos(newNumericPos) && numericKeypad(newNumericPos) != ' ' )
57
+ Some (copy(numericPos = newNumericPos))
58
+ else
59
+ None // out of keypad
60
+ }
75
61
76
- override def unitNeighbors (state : State ): IterableOnce [State ] = " <v>^A" .iterator.flatten(state.userPress).filter(s => code.startsWith(s.input))
62
+ def directionalPress (button : Char ): Option [State ] = directionalPoss match {
63
+ case Nil => numericPress(button)
64
+ case directionalPos :: newDirectionalPoss =>
65
+ button match {
66
+ case 'A' =>
67
+ val newButton = directionalKeypad(directionalPos)
68
+ copy(directionalPoss = newDirectionalPoss).directionalPress(newButton).map(newState =>
69
+ newState.copy(directionalPoss = directionalPos :: newState.directionalPoss)
70
+ )
71
+ case _ =>
72
+ val offset = directionalOffsets(button)
73
+ val newDirectionalPos = directionalPos + offset
74
+ if (directionalKeypad.containsPos(newDirectionalPos) && directionalKeypad(newDirectionalPos) != ' ' )
75
+ Some (copy(directionalPoss = newDirectionalPos :: newDirectionalPoss))
76
+ else
77
+ None // out of keypad
78
+ }
79
+ }
77
80
78
- override def isTargetNode ( state : State , dist : Int ): Boolean = state.input == code
81
+ def userPress ( button : Char ): Option [ State ] = directionalPress(button)
79
82
}
80
83
81
- BFS .search(graphSearch).target.get._2
82
- }
84
+ override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
83
85
86
+ val graphSearch = new GraphSearch [State ] with UnitNeighbors [State ] {
87
+ override val startNode : State = State (List .fill(directionalKeypads)(directionalKeypad.posOf('A' )), numericKeypad.posOf('A' ), " " )
84
88
85
- // copied & modified from 2024 day 10
86
- // TODO: extract to library?
87
- def pathSearch [A ](graphSearch : GraphSearch [A ] & UnitNeighbors [A ]): GraphSearch [List [A ]] & UnitNeighbors [List [A ]] = {
88
- new GraphSearch [List [A ]] with UnitNeighbors [List [A ]] {
89
- override val startNode : List [A ] = List (graphSearch.startNode)
89
+ override def unitNeighbors (state : State ): IterableOnce [State ] = " <v>^A" .iterator.flatten(state.userPress).filter(s => code.startsWith(s.input))
90
90
91
- override def unitNeighbors ( node : List [ A ] ): IterableOnce [ List [ A ]] =
92
- graphSearch.unitNeighbors(node.head).iterator.map(_ :: node)
91
+ override def isTargetNode ( state : State , dist : Int ): Boolean = state.input == code
92
+ }
93
93
94
- override def isTargetNode ( node : List [ A ], dist : Int ) : Boolean = graphSearch.isTargetNode(node.head, dist)
94
+ BFS .search( graphSearch).target.get._2
95
95
}
96
96
}
97
97
98
- private def keypadPaths (keypad : Grid [Char ]): Map [(Char , Char ), Set [Code ]] = {
99
- val box = Box (Pos .zero, Pos (keypad(0 ).size - 1 , keypad.size - 1 ))
100
- (for {
101
- startPos <- box.iterator
102
- if keypad(startPos) != ' '
103
- targetPos <- box.iterator
104
- if keypad(targetPos) != ' '
105
- } yield {
106
- val graphSearch = new GraphSearch [Pos ] with UnitNeighbors [Pos ] with TargetNode [Pos ] {
107
- override val startNode : Pos = startPos
108
-
109
- override def unitNeighbors (pos : Pos ): IterableOnce [Pos ] =
110
- Pos .axisOffsets.map(pos + _).filter(keypad.containsPos).filter(keypad(_) != ' ' )
111
-
112
- override val targetNode : Pos = targetPos
113
- }
114
- (keypad(targetPos), keypad(startPos)) -> // flipped because paths are reversed
115
- SimultaneousBFS .search(pathSearch(graphSearch))
116
- .nodes
117
- .filter(_.head == targetPos)
118
- .map(poss =>
119
- (poss lazyZip poss.tail)
120
- .map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 })
121
- .mkString
122
- )
123
- .toSet
124
- }).toMap
125
- }
98
+ object DynamicProgrammingSolution extends Solution {
99
+
100
+ // copied & modified from 2024 day 10
101
+ // TODO: extract to library?
102
+ def pathSearch [A ](graphSearch : GraphSearch [A ] & UnitNeighbors [A ]): GraphSearch [List [A ]] & UnitNeighbors [List [A ]] = {
103
+ new GraphSearch [List [A ]] with UnitNeighbors [List [A ]] {
104
+ override val startNode : List [A ] = List (graphSearch.startNode)
126
105
127
- private val numericPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(numericKeypad)
128
- private val directionalPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(directionalKeypad)
129
-
130
- // println(numericPaths)
131
-
132
- def shortestSequenceLength2 (code : Code , directionalKeypads : Int , i : Int = 0 ): Long = {
133
-
134
- val memo = mutable.Map .empty[(Code , Int ), Long ]
135
-
136
- def helper (code : Code , i : Int ): Long = {
137
- memo.getOrElseUpdate((code, i), {
138
- // assert(directionalKeypads == 0)
139
- code.foldLeft(('A' , 0L ))({ case ((prev, length), cur) =>
140
- val newLength =
141
- (for {
142
- path <- if (i == 0 ) numericPaths((prev, cur)) else directionalPaths((prev, cur))
143
- path2 = path + 'A'
144
- len =
145
- if (i == directionalKeypads)
146
- path2.length.toLong
147
- else
148
- helper(path2, i + 1 )
149
- } yield len).min
150
- (cur, length + newLength)
151
- })._2
152
- })
106
+ override def unitNeighbors (node : List [A ]): IterableOnce [List [A ]] =
107
+ graphSearch.unitNeighbors(node.head).iterator.map(_ :: node)
108
+
109
+ override def isTargetNode (node : List [A ], dist : Int ): Boolean = graphSearch.isTargetNode(node.head, dist)
110
+ }
153
111
}
154
112
155
- helper(code, 0 )
156
- }
113
+ private def keypadPaths (keypad : Grid [Char ]): Map [(Char , Char ), Set [Code ]] = {
114
+ val box = Box (Pos .zero, Pos (keypad(0 ).size - 1 , keypad.size - 1 ))
115
+ (for {
116
+ startPos <- box.iterator
117
+ if keypad(startPos) != ' '
118
+ targetPos <- box.iterator
119
+ if keypad(targetPos) != ' '
120
+ } yield {
121
+ val graphSearch = new GraphSearch [Pos ] with UnitNeighbors [Pos ] with TargetNode [Pos ] {
122
+ override val startNode : Pos = startPos
123
+
124
+ override def unitNeighbors (pos : Pos ): IterableOnce [Pos ] =
125
+ Pos .axisOffsets.map(pos + _).filter(keypad.containsPos).filter(keypad(_) != ' ' )
126
+
127
+ override val targetNode : Pos = targetPos
128
+ }
129
+ (keypad(targetPos), keypad(startPos)) -> // flipped because paths are reversed
130
+ SimultaneousBFS .search(pathSearch(graphSearch))
131
+ .nodes
132
+ .filter(_.head == targetPos)
133
+ .map(poss =>
134
+ (poss lazyZip poss.tail)
135
+ .map({ case (p2, p1) => directionalOffsets.find(_._2 == p1 - p2).get._1 })
136
+ .mkString
137
+ )
138
+ .toSet
139
+ }).toMap
140
+ }
157
141
142
+ private val numericPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(numericKeypad)
143
+ private val directionalPaths : Map [(Char , Char ), Set [Code ]] = keypadPaths(directionalKeypad)
144
+
145
+ override def shortestSequenceLength (code : Code , directionalKeypads : Int ): Long = {
146
+ val memo = mutable.Map .empty[(Code , Int ), Long ]
147
+
148
+ def helper (code : Code , i : Int ): Long = {
149
+ memo.getOrElseUpdate((code, i), {
150
+ // assert(directionalKeypads == 0)
151
+ code.foldLeft(('A' , 0L ))({ case ((prev, length), cur) =>
152
+ val newLength =
153
+ (for {
154
+ path <- if (i == 0 ) numericPaths((prev, cur)) else directionalPaths((prev, cur))
155
+ path2 = path + 'A'
156
+ len =
157
+ if (i == directionalKeypads)
158
+ path2.length.toLong
159
+ else
160
+ helper(path2, i + 1 )
161
+ } yield len).min
162
+ (cur, length + newLength)
163
+ })._2
164
+ })
165
+ }
158
166
159
- def codeComplexity (code : Code , directionalKeypads : Int ): Long = {
160
- val numericPart = code.dropRight(1 ).toInt
161
- shortestSequenceLength2(code, directionalKeypads) * numericPart
167
+ helper(code, 0 )
168
+ }
162
169
}
163
170
164
- def sumCodeComplexity (codes : Seq [Code ], directionalKeypads : Int ): Long = codes.map(codeComplexity(_, directionalKeypads)).sum
165
-
166
171
def parseCodes (input : String ): Seq [Code ] = input.linesIterator.toSeq
167
172
168
173
lazy val input : String = scala.io.Source .fromInputStream(getClass.getResourceAsStream(" day21.txt" )).mkString.trim
169
174
175
+ val part1DirectionalKeypads = 2
176
+ val part2DirectionalKeypads = 25
177
+
170
178
def main (args : Array [String ]): Unit = {
171
- println(sumCodeComplexity(parseCodes(input), 2 ))
172
- println(sumCodeComplexity(parseCodes(input), 25 ))
179
+ import DynamicProgrammingSolution ._
180
+ println(sumCodeComplexity(parseCodes(input), part1DirectionalKeypads))
181
+ println(sumCodeComplexity(parseCodes(input), part2DirectionalKeypads))
173
182
174
183
// part 2: 1301407762 - too low (Int overflowed in shortestSequenceLength2)
175
184
}
0 commit comments