@@ -19,81 +19,106 @@ package adventofcode.day16
19
19
import adventofcode.util.geom.plane.Direction
20
20
import adventofcode.util.geom.plane.Point2D
21
21
import adventofcode.util.graph.Graphs
22
+ import adventofcode.util.graph.ShortestPaths
22
23
import adventofcode.util.grid.TextGrid
23
24
24
- data class State (val position : Point2D , val orientation : Direction ) {
25
- fun moveForward () = State (position + orientation, orientation)
26
- fun turnLeft () = State (position, orientation.turnLeft())
27
- fun turnRight () = State (position, orientation.turnRight())
28
- }
25
+ fun String.countBestTiles (): Int = ReindeerMaze (this ).countBestTiles()
26
+
27
+ fun String.findLowestScore () = ReindeerMaze (this ).findLowestScore()
29
28
30
- private fun Direction.turnRight () = when (this ) {
31
- Direction .NORTH -> Direction .EAST
32
- Direction .EAST -> Direction .SOUTH
33
- Direction .SOUTH -> Direction .WEST
34
- else -> Direction .NORTH
29
+ data class Pose (val position : Point2D , val orientation : Direction ) {
30
+ fun goForward () = Pose (position + orientation, orientation)
31
+ fun goBack () = Pose (position - orientation, orientation)
32
+ fun turnLeft () = Pose (
33
+ position, when (orientation) {
34
+ Direction .NORTH -> Direction .WEST
35
+ Direction .WEST -> Direction .SOUTH
36
+ Direction .SOUTH -> Direction .EAST
37
+ else -> Direction .NORTH
38
+ }
39
+ )
40
+
41
+ fun turnRight () = Pose (
42
+ position, when (orientation) {
43
+ Direction .NORTH -> Direction .EAST
44
+ Direction .EAST -> Direction .SOUTH
45
+ Direction .SOUTH -> Direction .WEST
46
+ else -> Direction .NORTH
47
+ }
48
+ )
49
+
50
+ fun distanceTo (other : Pose ) = if (orientation == other.orientation) 1.0 else 1000.0
35
51
}
36
52
37
- private fun Direction.turnLeft () = when (this ) {
38
- Direction .NORTH -> Direction .WEST
39
- Direction .WEST -> Direction .SOUTH
40
- Direction .SOUTH -> Direction .EAST
41
- else -> Direction .NORTH
53
+ class Traversal (val path : List <Pose >, val distance : Double ) {
54
+ val visited = path.toSet()
42
55
}
43
56
44
- fun String. findBestTiles (): Int {
45
- val maze = TextGrid (lines())
46
- val start = State (maze.coordinates().find { maze[it] == ' S' } !! , Direction . EAST )
57
+ private const val WALL = ' # '
58
+ private const val END = ' E '
59
+ private const val START = ' S'
47
60
48
- val neighborsFn: (State ) -> List <State > = { state ->
49
- listOf (state.moveForward(), state.turnRight(), state.turnLeft())
50
- .filter { maze[it.position] != ' #' }
51
- }
52
- val reachable = Graphs .reachable(start, neighbors = neighborsFn)
53
- val ends = reachable.filter { maze[it.position] == ' E' }
54
- val memory = mutableMapOf<Pair <State , State >, Double > ()
55
- val weight: (State , State ) -> Double =
56
- { s1, s2 -> memory.computeIfAbsent(Pair (s1, s2)) { (s1, s2) -> score(s1, s2) } }
57
- val lowestScore =
58
- ends.minOf { end -> Graphs .shortestPaths(start, reachable, neighborsFn, weight).distanceTo(end).toInt() }
59
-
60
- val shortestPaths = ends
61
- .asSequence()
62
- .flatMap { end ->
63
- val shortestPathsForEnd =
64
- Graphs .shortestPaths(start, end, reachable, neighborsFn, weight)
65
- .takeWhile { path -> path.score().toInt() == lowestScore }
66
-
67
- shortestPathsForEnd
68
- }.toList()
69
-
70
- val bestTiles = shortestPaths
71
- .flatten()
72
- .map { it.position }
73
- .toSet()
74
- return bestTiles.size
61
+ class ReindeerMaze (input : String ) {
75
62
76
- }
63
+ private val maze = TextGrid (input.lines())
77
64
78
- fun List<State>. score () = zipWithNext().sumOf { (s1, s2) -> score(s1, s2) }
65
+ private fun Pose. isWall () = maze[position] == WALL
79
66
80
- private fun score (s1 : State , s2 : State ) = if (s1.orientation == s2.orientation) 1.0 else 1000.0
67
+ fun successorsOf (pose : Pose ) = neighborsFn(END ) { it.goForward() }(pose)
68
+ fun predecessorsOf (pose : Pose ) = neighborsFn(START ) { it.goBack() }(pose)
81
69
82
- fun String.findLowestScore (): Int {
83
- val maze = TextGrid (lines())
84
- val start = State (maze.coordinates().find { maze[it] == ' S' }!! , Direction .EAST )
85
- val neighborsFn: (State ) -> List <State > = { state ->
86
- listOf (state.moveForward(), state.turnRight(), state.turnLeft())
87
- .filter { maze[it.position] != ' #' }
70
+ private fun neighborsFn (goal : Char , advance : (Pose ) -> Pose ): (Pose ) -> List <Pose > = { state ->
71
+ val neighbors = mutableListOf<Pose >()
72
+ if (maze[state.position] != goal) {
73
+ if (! advance(state).isWall()) {
74
+ neighbors.add(advance(state))
75
+ }
76
+ if (! advance(state.turnLeft()).isWall()) {
77
+ neighbors.add(state.turnLeft())
78
+ }
79
+ if (! advance(state.turnRight()).isWall()) {
80
+ neighbors.add(state.turnRight())
81
+ }
82
+ }
83
+ neighbors
88
84
}
89
- val reachable = Graphs .reachable(start, neighbors = neighborsFn)
90
- return reachable.filter { maze[it.position] == ' E' }
91
- .minOf { end ->
92
- Graphs .shortestPaths(
93
- start,
94
- reachable,
95
- neighborsFn
96
- ) { s1, s2 -> score(s1, s2) }.distanceTo(end)
85
+
86
+ private fun shortestPaths (): ShortestPaths <Pose > {
87
+ val start = Pose (maze.coordinates().find { maze[it] == START }!! , Direction .EAST )
88
+ val vertices = Graphs .reachable(start, neighbors = ::successorsOf)
89
+ return Graphs .shortestPaths(start, vertices, ::successorsOf) { p1, p2 -> p1.distanceTo(p2) }
90
+ }
91
+
92
+ fun findLowestScore (): Int {
93
+ val shortestPaths = shortestPaths()
94
+ val ends = shortestPaths.reachable().filter { maze[it.position] == END }
95
+ return ends.minOf { end -> shortestPaths.distanceTo(end) }.toInt()
96
+ }
97
+
98
+ fun countBestTiles (): Int {
99
+ val shortestPaths = shortestPaths()
100
+ val ends = shortestPaths.reachable().filter { maze[it.position] == END }
101
+ val shortestDistance = ends.minOf { end -> shortestPaths.distanceTo(end) }
102
+ val stack = mutableListOf<Traversal >()
103
+ ends.forEach { stack.addFirst(Traversal (listOf (it), 0.0 )) }
104
+ val allShortestPaths = mutableListOf<List <Pose >>()
105
+ while (stack.isNotEmpty()) {
106
+ val traversal = stack.removeFirst()
107
+ val currentState = traversal.path.last()
108
+ if (maze[currentState.position] == START ) {
109
+ allShortestPaths.add(traversal.path)
110
+ } else {
111
+ predecessorsOf(currentState)
112
+ .filter { it !in traversal.visited }
113
+ .filter { shortestPaths.distanceTo(it) + traversal.distance <= shortestDistance }
114
+ .forEach { pred ->
115
+ val newPath = traversal.path + pred
116
+ val newDistance = traversal.distance + pred.distanceTo(currentState)
117
+ stack.addFirst(Traversal (newPath, newDistance))
118
+ }
119
+ }
97
120
}
98
- .toInt()
99
- }
121
+ return allShortestPaths.flatten().map { it.position }.distinct().size
122
+ }
123
+ }
124
+
0 commit comments