Skip to content

Commit 9b7a432

Browse files
committed
Improve solution 2018-15, part 2 (Beverage Bandits)
By stop searching for paths to further targets as soon as all paths to targets that are the minimal distance away, on an M$ Surface 8 part 2 terminates in 12 seconds instead of approx 1 minute.
1 parent a544a63 commit 9b7a432

File tree

5 files changed

+66
-11
lines changed

5 files changed

+66
-11
lines changed

src/main/kotlin/de/ronny_h/aoc/extensions/graphs/shortestpath/Dijkstra.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ data class DijkstraResult<V>(val distances: Map<V, Int>, val predecessors: Map<V
1515
/**
1616
* @return A list containing one shortest path from [source] to each target in [targets] in the [graph].
1717
*/
18-
fun <V> dijkstra(graph: Graph<V>, source: V, targets: List<V>): List<ShortestPath<V>> {
19-
val dijkstraResult = dijkstra(graph, source)
18+
fun <V> dijkstraShortestPaths(
19+
graph: Graph<V>,
20+
source: V,
21+
targets: List<V>,
22+
stopAfterMinimalPathsAreFound: Boolean = false,
23+
): List<ShortestPath<V>> {
24+
val dijkstraResult = dijkstra(graph, source, targets, stopAfterMinimalPathsAreFound)
2025
return targets.mapNotNull { reconstructPath(dijkstraResult, source, it) }
2126
}
2227

@@ -28,14 +33,30 @@ fun <V> dijkstra(graph: Graph<V>, source: V, targets: List<V>): List<ShortestPat
2833
*
2934
* While traversing, when more than one vertex has the same minimal distance to the current one, the vertex that comes
3035
* first in the [graph]'s `vertices` list is chosen.
36+
*
37+
* @param stopAfterMinimalPathsAreFound If `true`, the algorithm stops as soon as all minimal paths (i.e. paths of the
38+
* same, minimal length to arbitrary vertices in the [targets] list) have been found.
3139
*/
32-
fun <V> dijkstra(graph: Graph<V>, source: V): DijkstraResult<V> {
40+
fun <V> dijkstra(
41+
graph: Graph<V>,
42+
source: V,
43+
targets: List<V>,
44+
stopAfterMinimalPathsAreFound: Boolean = false,
45+
): DijkstraResult<V> {
3346
val dist = mutableMapOf(source to 0).withDefault { LARGE_VALUE }
3447
val prev = mutableMapOf<V, V>()
3548
val q = graph.vertices.toMutableList()
3649

50+
var minimalDistance = LARGE_VALUE
3751
while (q.isNotEmpty()) {
3852
val u = q.minBy { dist.getValue(it) }
53+
if (stopAfterMinimalPathsAreFound && u in targets) {
54+
if (minimalDistance == LARGE_VALUE) {
55+
minimalDistance = dist.getValue(u)
56+
} else if (minimalDistance < dist.getValue(u)) {
57+
break
58+
}
59+
}
3960
q.remove(u)
4061

4162
for (v in q) {

src/main/kotlin/de/ronny_h/aoc/extensions/grids/Grid.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package de.ronny_h.aoc.extensions.grids
33
import de.ronny_h.aoc.extensions.graphs.shortestpath.Graph
44
import de.ronny_h.aoc.extensions.graphs.shortestpath.ShortestPath
55
import de.ronny_h.aoc.extensions.graphs.shortestpath.aStarAllPaths
6-
import de.ronny_h.aoc.extensions.graphs.shortestpath.dijkstra
6+
import de.ronny_h.aoc.extensions.graphs.shortestpath.dijkstraShortestPaths
77
import java.io.ByteArrayOutputStream
88
import java.io.PrintStream
99
import java.nio.charset.StandardCharsets
@@ -218,6 +218,7 @@ abstract class Grid<T>(
218218
fun shortestPaths(
219219
start: Coordinates,
220220
goals: List<Coordinates>,
221+
stopAfterMinimalPathsAreFound: Boolean = false,
221222
isObstacle: (T) -> Boolean = { it == nullElement },
222223
): List<ShortestPath<Coordinates>> {
223224
val graph = Graph(
@@ -228,7 +229,7 @@ abstract class Grid<T>(
228229
if (to in from.neighbours().filter { !isObstacle(getAt(it)) }) 1 else null
229230
}
230231
)
231-
return dijkstra(graph, start, goals)
232+
return dijkstraShortestPaths(graph, start, goals, stopAfterMinimalPathsAreFound)
232233
}
233234

234235
/**

src/main/kotlin/de/ronny_h/aoc/year2018/day15/BeverageBandits.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class CombatArea(input: List<String>, val elfAttackPower: Int = 3) : SimpleCharG
112112
val shortestPaths = shortestPaths(
113113
start = position,
114114
goals = targetsInRange,
115+
stopAfterMinimalPathsAreFound = true,
115116
isObstacle = { it != cavern })
116117

117118
if (shortestPaths.isEmpty()) {

src/test/kotlin/de/ronny_h/aoc/extensions/graphs/shortestpath/ShortestPathTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class ShortestPathTest : StringSpec({
6363
)
6464
)
6565
aStar(start, goal::positionEquals, neighbours, d, h) shouldBe ShortestPath(listOf(start, goal), 1)
66-
dijkstra(Graph(listOf(start, goal), d), start, listOf(goal)) shouldBe listOf(
66+
dijkstraShortestPaths(Graph(listOf(start, goal), d), start, listOf(goal)) shouldBe listOf(
6767
ShortestPath(
6868
listOf(start, goal),
6969
1
@@ -100,7 +100,7 @@ class ShortestPathTest : StringSpec({
100100
ShortestPath(listOf(start, a, goal), 10)
101101

102102
val edges: (Node, Node) -> Int? = { m, n -> distances[m to n] }
103-
dijkstra(Graph(listOf(start, a, b, goal), edges), start, listOf(goal)) shouldBe
103+
dijkstraShortestPaths(Graph(listOf(start, a, b, goal), edges), start, listOf(goal)) shouldBe
104104
listOf(ShortestPath(listOf(start, a, goal), 10))
105105
}
106106

@@ -128,7 +128,7 @@ class ShortestPathTest : StringSpec({
128128
listOf(ShortestPath(listOf(start, a, goal), 9))
129129
aStar(start, goal::positionEquals, { n -> neighbours.getValue(n) }, d, h) shouldBe
130130
ShortestPath(listOf(start, a, goal), 9)
131-
dijkstra(Graph(listOf(start, a, goal), d), start, listOf(goal)) shouldBe
131+
dijkstraShortestPaths(Graph(listOf(start, a, goal), d), start, listOf(goal)) shouldBe
132132
listOf(ShortestPath(listOf(start, a, goal), 9))
133133
}
134134

@@ -160,7 +160,7 @@ class ShortestPathTest : StringSpec({
160160
listOf(ShortestPath(listOf(start, a, goal), 9))
161161
aStar(start, goal::positionEquals, { n -> neighbours.getValue(n) }, d, h) shouldBe
162162
ShortestPath(listOf(start, a, goal), 9)
163-
dijkstra(Graph(listOf(start, a, goal), d), start, listOf(goal)) shouldBe
163+
dijkstraShortestPaths(Graph(listOf(start, a, goal), d), start, listOf(goal)) shouldBe
164164
listOf(ShortestPath(listOf(start, a, goal), 9))
165165
}
166166

@@ -193,7 +193,7 @@ class ShortestPathTest : StringSpec({
193193
ShortestPath(listOf(start, a, b, goal), 9)
194194

195195
val edges: (Node, Node) -> Int? = { m, n -> distances[m to n] }
196-
dijkstra(Graph(listOf(start, a, b, goal), edges), start, listOf(goal)) shouldBe
196+
dijkstraShortestPaths(Graph(listOf(start, a, b, goal), edges), start, listOf(goal)) shouldBe
197197
listOf(ShortestPath(listOf(start, a, b, goal), 9))
198198
}
199199

@@ -205,7 +205,7 @@ class ShortestPathTest : StringSpec({
205205
val h: (Node) -> Int = { n -> n.position taxiDistanceTo goal.position }
206206

207207
aStarAllPaths(start, goal::positionEquals, { emptyList() }, d, h) shouldBe emptyList()
208-
dijkstra(Graph(listOf(start, goal), { _, _ -> null }), start, listOf(goal)) shouldBe emptyList()
208+
dijkstraShortestPaths(Graph(listOf(start, goal), { _, _ -> null }), start, listOf(goal)) shouldBe emptyList()
209209

210210
shouldThrow<IllegalStateException> { aStar(start, goal::positionEquals, { emptyList() }, d, h) }
211211
}

src/test/kotlin/de/ronny_h/aoc/extensions/grids/SimpleCharGridTest.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,38 @@ class SimpleCharGridTest : StringSpec({
179179
)
180180
}
181181

182+
"the Dijkstra implementation stopAfterMinimalPathsAreFound if that option is set" {
183+
val input = """
184+
SoG
185+
o.o
186+
G.G
187+
..G
188+
""".asList()
189+
SimpleCharGrid(input)
190+
.shortestPaths(
191+
start = C(0, 0),
192+
goals = listOf(C(0, 2), C(2, 0), C(2, 2), C(3, 2)),
193+
stopAfterMinimalPathsAreFound = true,
194+
) shouldBe listOf(
195+
ShortestPath(
196+
listOf(
197+
C(0, 0), C(0, 1), C(0, 2),
198+
), 2
199+
),
200+
ShortestPath(
201+
listOf(
202+
C(0, 0), C(1, 0), C(2, 0),
203+
), 2
204+
),
205+
// to know when all minimal paths are found, the algorithm has to continue to the next larger one
206+
ShortestPath(
207+
listOf(
208+
C(0, 0), C(0, 1), C(0, 2), C(1, 2), C(2, 2),
209+
), 4
210+
),
211+
)
212+
}
213+
182214
"the Dijkstra implementation ignores unreachable goals" {
183215
val input = """
184216
1#2

0 commit comments

Comments
 (0)